Repository: felangel/bloc (dart)

Issue:
refactor(bloc): calculate composite object hashes through `Object.hashAll` 
Hi!

[In Dart version `2.14` class `Object` obtained a new static method – `hashAll`.](https://api.dart.dev/stable/2.14.1/dart-core/Object/hashAll.html) 

[It is a recommended method of computing a hash of multiple objects since the bitwise XOR (`^`) is symmetric and is not secure.](https://stackoverflow.com/questions/5889238/why-is-xor-the-default-way-to-combine-hashes)

There are two places where bitwise XOR is used to combine hashes: [here](https://github.com/felangel/bloc/blob/master/packages/bloc/lib/src/change.dart#L27) and [here](https://github.com/felangel/bloc/blob/master/packages/bloc/lib/src/transition.dart#L31).

The package's current minimal version is `2.12`, but I think that it's worth keeping in mind for the future.

Pull request description:
<!--
  Thanks for contributing!

  Provide a description of your changes below and a general summary in the title

  Please look at the following checklist to ensure that your PR can be accepted quickly:
-->

## Status

**READY**

## Breaking Changes

NO

## Description

<!--- Describe your changes in detail -->
- refactor(bloc): use `Object.hashAll` (closes https://github.com/felangel/bloc/issues/3090)
  - bumps minimum Dart SDK to 2.14

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ] ✨ New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
- [X] 🧹 Code refactor
- [ ] ✅ Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore

Patch:
diff --git a/packages/bloc/lib/src/change.dart b/packages/bloc/lib/src/change.dart
index ed7d3e98379..5b1a001f968 100644
--- a/packages/bloc/lib/src/change.dart
+++ b/packages/bloc/lib/src/change.dart
@@ -24,7 +24,7 @@ class Change<State> {
           nextState == other.nextState;
 
   @override
-  int get hashCode => currentState.hashCode ^ nextState.hashCode;
+  int get hashCode => Object.hashAll([currentState, nextState]);
 
   @override
   String toString() {
diff --git a/packages/bloc/lib/src/transition.dart b/packages/bloc/lib/src/transition.dart
index 4603fa96448..336b8c71b9a 100644
--- a/packages/bloc/lib/src/transition.dart
+++ b/packages/bloc/lib/src/transition.dart
@@ -27,9 +27,7 @@ class Transition<Event, State> extends Change<State> {
           nextState == other.nextState;
 
   @override
-  int get hashCode {
-    return currentState.hashCode ^ event.hashCode ^ nextState.hashCode;
-  }
+  int get hashCode => Object.hashAll([currentState, event, nextState]);
 
   @override
   String toString() {
diff --git a/packages/bloc/pubspec.yaml b/packages/bloc/pubspec.yaml
index a973adac949..d3291da33a1 100644
--- a/packages/bloc/pubspec.yaml
+++ b/packages/bloc/pubspec.yaml
@@ -9,7 +9,7 @@ topics: [bloc, state-management]
 funding: [https://github.com/sponsors/felangel]
 
 environment:
-  sdk: ">=2.12.0 <4.0.0"
+  sdk: ">=2.14.0 <4.0.0"
 
 dependencies:
   meta: ^1.3.0


Test patch:
diff --git a/packages/bloc/test/blocs/async/async_event.dart b/packages/bloc/test/blocs/async/async_event.dart
index a2e775a23c8..0f8617f87aa 100644
--- a/packages/bloc/test/blocs/async/async_event.dart
+++ b/packages/bloc/test/blocs/async/async_event.dart
@@ -13,7 +13,7 @@ class AsyncEvent {
       other is AsyncEvent && runtimeType == other.runtimeType;
 
   @override
-  int get hashCode => runtimeType.hashCode;
+  int get hashCode => Object.hashAll([runtimeType]);
 
   @override
   String toString() => 'AsyncEvent';
diff --git a/packages/bloc/test/blocs/async/async_state.dart b/packages/bloc/test/blocs/async/async_state.dart
index aee3feaf540..f42526dbd52 100644
--- a/packages/bloc/test/blocs/async/async_state.dart
+++ b/packages/bloc/test/blocs/async/async_state.dart
@@ -43,8 +43,7 @@ class AsyncState {
           isSuccess == other.isSuccess;
 
   @override
-  int get hashCode =>
-      isLoading.hashCode ^ hasError.hashCode ^ isSuccess.hashCode;
+  int get hashCode => Object.hashAll([isLoading, hasError, isSuccess]);
 
   @override
   String toString() =>
diff --git a/packages/bloc/test/transition_test.dart b/packages/bloc/test/transition_test.dart
index d39e5e37510..9018adcf838 100644
--- a/packages/bloc/test/transition_test.dart
+++ b/packages/bloc/test/transition_test.dart
@@ -25,7 +25,7 @@ class CounterEvent extends TransitionEvent {
           eventData == other.eventData;
 
   @override
-  int get hashCode => eventData.hashCode;
+  int get hashCode => Object.hashAll([eventData]);
 }
 
 class CounterState extends TransitionState {
@@ -41,7 +41,7 @@ class CounterState extends TransitionState {
           count == other.count;
 
   @override
-  int get hashCode => count.hashCode;
+  int get hashCode => Object.hashAll([count]);
 }
 
 void main() {
@@ -78,7 +78,7 @@ void main() {
         const change = Change<int>(currentState: 0, nextState: 1);
         expect(
           change.hashCode,
-          change.currentState.hashCode ^ change.nextState.hashCode,
+          Object.hashAll([change.currentState, change.nextState]),
         );
       });
     });
@@ -196,9 +196,11 @@ void main() {
         );
         expect(
           transition.hashCode,
-          transition.currentState.hashCode ^
-              transition.event.hashCode ^
-              transition.nextState.hashCode,
+          Object.hashAll([
+            transition.currentState,
+            transition.event,
+            transition.nextState,
+          ]),
         );
       });
     });