Skip to content

Commit 1c5ab58

Browse files
authored
Merge pull request #424 from jinyus/nav-bug-fix
fix: use identityHashCode for context
2 parents 72f13fa + 65a471e commit 1c5ab58

File tree

2 files changed

+99
-3
lines changed

2 files changed

+99
-3
lines changed

‎packages/signals_flutter/lib/src/watch/extension.dart‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ T watchSignal<T>(
1616
}
1717
}
1818
if (ctx is Element) {
19-
final key = ctx.hashCode;
19+
final key = identityHashCode(ctx);
2020
if (_elementRefs[key] == null) {
2121
final label = [
2222
'widget',
2323
ctx.widget.runtimeType.toString(),
24-
ctx.widget.hashCode.toString(),
24+
identityHashCode(ctx.widget).toString(),
2525
].join('=');
2626
final watcher = ElementWatcher(
2727
key,
@@ -47,6 +47,6 @@ void unwatchSignal<T>(BuildContext context, core.ReadonlySignal<T> signal) {
4747
return state.unwatchSignal(signal);
4848
}
4949
}
50-
final key = ctx.hashCode;
50+
final key = identityHashCode(ctx);
5151
_elementRefs.remove(key)?.unwatch(signal);
5252
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// ignore_for_file: hash_and_equals
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:signals_flutter/signals_flutter.dart';
6+
7+
void main() {
8+
testWidgets(
9+
'watch with forced hashCode collision demonstrates bug',
10+
(WidgetTester tester) async {
11+
final counter = signal(0);
12+
const collisionHash = 999999;
13+
14+
await tester.pumpWidget(
15+
MaterialApp(
16+
home: _CollisionWidget(
17+
counter: counter,
18+
forcedHash: collisionHash,
19+
),
20+
),
21+
);
22+
23+
await tester.pumpAndSettle();
24+
expect(find.text('Count: 0'), findsOneWidget);
25+
26+
counter.value = 1;
27+
await tester.pumpAndSettle();
28+
expect(find.text('Count: 1'), findsOneWidget);
29+
30+
await tester.pumpWidget(
31+
const MaterialApp(
32+
home: Scaffold(body: Text('Away')),
33+
),
34+
);
35+
36+
await tester.pumpAndSettle();
37+
expect(find.text('Away'), findsOneWidget);
38+
39+
await tester.pumpWidget(
40+
MaterialApp(
41+
home: _CollisionWidget(
42+
counter: counter,
43+
forcedHash: collisionHash,
44+
),
45+
),
46+
);
47+
48+
await tester.pumpAndSettle();
49+
expect(find.text('Count: 1'), findsOneWidget);
50+
51+
counter.value = 2;
52+
await tester.pumpAndSettle();
53+
54+
expect(
55+
find.text('Count: 2'),
56+
findsOneWidget,
57+
reason: 'Widget should rebuild when signal changes after re-navigation',
58+
);
59+
},
60+
);
61+
}
62+
63+
class _CollisionWidget extends StatefulWidget {
64+
const _CollisionWidget({
65+
required this.counter,
66+
required this.forcedHash,
67+
});
68+
69+
final Signal<int> counter;
70+
final int forcedHash;
71+
72+
@override
73+
State<_CollisionWidget> createState() => _CollisionWidgetStateWithHash();
74+
75+
@override
76+
StatefulElement createElement() => _CollisionElement(this);
77+
}
78+
79+
class _CollisionElement extends StatefulElement {
80+
_CollisionElement(super.widget);
81+
82+
@override
83+
int get hashCode => (widget as _CollisionWidget).forcedHash;
84+
}
85+
86+
class _CollisionWidgetStateWithHash extends State<_CollisionWidget> {
87+
@override
88+
Widget build(BuildContext context) {
89+
final count = widget.counter.watch(context);
90+
return Scaffold(
91+
body: Center(
92+
child: Text('Count: $count'),
93+
),
94+
);
95+
}
96+
}

0 commit comments

Comments
 (0)