diff --git a/packages/flutter_hooks/CHANGELOG.md b/packages/flutter_hooks/CHANGELOG.md index 6acf7879..0f47d765 100644 --- a/packages/flutter_hooks/CHANGELOG.md +++ b/packages/flutter_hooks/CHANGELOG.md @@ -1,5 +1,8 @@ -## Unreleased minor +## Unreleased major +- **Breaking**: `keys` comparison has changed under the following conditions: + - Change from `double.nan` to `double.nan` preserves the state. + - Change from `0.0` to `-0.0` or vice versa does NOT preserve the state. - Added `useOnStreamChange` (thanks to @jezsung) ## 0.19.0 - 2023-07-10 diff --git a/packages/flutter_hooks/lib/src/framework.dart b/packages/flutter_hooks/lib/src/framework.dart index d46bcd8f..d3104a9b 100644 --- a/packages/flutter_hooks/lib/src/framework.dart +++ b/packages/flutter_hooks/lib/src/framework.dart @@ -153,6 +153,10 @@ Calling them outside of build method leads to an unstable state and is therefore /// /// - `hook1.keys == hook2.keys` (typically if the list is immutable) /// - If there's any difference in the content of [Hook.keys], using `operator==`. + /// + /// There are exceptions when comparing [Hook.keys] before using `operator==`: + /// - A state is preserved when one of the [Hook.keys] is [double.nan]. + /// - A state is NOT preserved when one of the [Hook.keys] is changed from 0.0 to -0.0. static bool shouldPreserveState(Hook hook1, Hook hook2) { final p1 = hook1.keys; final p2 = hook2.keys; @@ -172,7 +176,23 @@ Calling them outside of build method leads to an unstable state and is therefore if (!i1.moveNext() || !i2.moveNext()) { return true; } - if (i1.current != i2.current) { + + final curr1 = i1.current; + final curr2 = i2.current; + + if (curr1 is num && curr2 is num) { + // Checks if both are NaN + if (curr1.isNaN && curr2.isNaN) { + return true; + } + + // Checks if one is 0.0 and the other is -0.0 + if (curr1 == 0 && curr2 == 0) { + return curr1.isNegative == curr2.isNegative; + } + } + + if (curr1 != curr2) { return false; } } diff --git a/packages/flutter_hooks/test/use_effect_test.dart b/packages/flutter_hooks/test/use_effect_test.dart index 66a76f4c..d3a0c430 100644 --- a/packages/flutter_hooks/test/use_effect_test.dart +++ b/packages/flutter_hooks/test/use_effect_test.dart @@ -249,6 +249,55 @@ void main() { verifyNoMoreInteractions(disposerA); verifyNoMoreInteractions(effect); }); + + testWidgets( + 'does NOT call effect when one of keys is NaN and others are same', + (tester) async { + parameters = [double.nan]; + await tester.pumpWidget(builder()); + + verifyInOrder([ + effect(), + unrelated(), + ]); + verifyNoMoreInteractions(effect); + + parameters = [double.nan]; + await tester.pumpWidget(builder()); + + verifyNoMoreInteractions(effect); + }); + + testWidgets( + 'calls effect when one of keys is changed from 0.0 to -0.0 and vice versa', + (tester) async { + parameters = [0.0]; + await tester.pumpWidget(builder()); + + verifyInOrder([ + effect(), + unrelated(), + ]); + verifyNoMoreInteractions(effect); + + parameters = [-0.0]; + await tester.pumpWidget(builder()); + + verifyInOrder([ + effect(), + unrelated(), + ]); + verifyNoMoreInteractions(effect); + + parameters = [0.0]; + await tester.pumpWidget(builder()); + + verifyInOrder([ + effect(), + unrelated(), + ]); + verifyNoMoreInteractions(effect); + }); } class MockEffect extends Mock {