diff --git a/.fvmrc b/.fvmrc index 1d524b3..084b2bc 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.38.7" + "flutter": "3.41.9" } diff --git a/lib/src/base_notifier.dart b/lib/src/base_notifier.dart index 4062f28..fc342f6 100644 --- a/lib/src/base_notifier.dart +++ b/lib/src/base_notifier.dart @@ -75,8 +75,8 @@ abstract class BaseNotifier extends Notifier { /// Because of this, [init] must be **idempotent** and must not contain /// one-time initialization logic. /// - /// It is safe to use [ref.watch] and [ref.listen] here, but be aware that - /// changes to watched providers will cause [build] and therefor also [init] to re-run. + /// It is safe to use [Ref.watch] here, but keep in mind: whenever a watched + /// provider changes, [build] — and therefore [init] — will re-run. /// /// See also the documentation of [Notifier.build]. @protected diff --git a/test/base_notifier_init_rerun_test.dart b/test/base_notifier_init_rerun_test.dart new file mode 100644 index 0000000..df4af62 --- /dev/null +++ b/test/base_notifier_init_rerun_test.dart @@ -0,0 +1,102 @@ +import 'package:tapped_riverpod/tapped_riverpod.dart'; +import 'package:test/test.dart'; + +void main() { + test( + "ref.watch in init causes init to re-run when watched provider changes", + () async { + final container = ProviderContainer(); + addTearDown(container.dispose); + + final notifier = container.read(_watchNotifierProvider.notifier); + + expect(notifier.initCallCount, 1); + expect(container.read(_watchNotifierProvider), 0); + + container.read(_counterProvider.notifier).state = 1; + // Allow rebuild to propagate. + await Future.delayed(Duration.zero); + + expect(container.read(_watchNotifierProvider), 1); + expect( + notifier.initCallCount, + 2, + reason: "init should re-run when a watched provider changes", + ); + }, + ); + + test( + "ref.listen in init does NOT cause init to re-run; only the callback is invoked", + () async { + final container = ProviderContainer(); + addTearDown(container.dispose); + + final notifier = container.read(_listenNotifierProvider.notifier); + + expect(notifier.initCallCount, 1); + expect(notifier.listenCallbackCount, 0); + + container.read(_counterProvider.notifier).state = 1; + await Future.delayed(Duration.zero); + + expect( + notifier.initCallCount, + 1, + reason: "init should NOT re-run when a listened provider changes", + ); + expect( + notifier.listenCallbackCount, + 1, + reason: "The listen callback should be invoked instead", + ); + + container.read(_counterProvider.notifier).state = 2; + await Future.delayed(Duration.zero); + + expect(notifier.initCallCount, 1); + expect(notifier.listenCallbackCount, 2); + }, + ); +} + +final _counterProvider = NotifierProvider<_CounterNotifier, int>( + () => _CounterNotifier(), +); + +class _CounterNotifier extends Notifier { + @override + int build() => 0; +} + +final _watchNotifierProvider = NotifierProvider<_WatchNotifier, int>( + () => _WatchNotifier(), +); + +class _WatchNotifier extends BaseNotifier { + int initCallCount = 0; + + @override + int init() { + initCallCount++; + return ref.watch(_counterProvider); + } +} + +final _listenNotifierProvider = NotifierProvider<_ListenNotifier, int>( + () => _ListenNotifier(), +); + +class _ListenNotifier extends BaseNotifier { + int initCallCount = 0; + int listenCallbackCount = 0; + + @override + int init() { + initCallCount++; + ref.listen(_counterProvider, (_, _) { + listenCallbackCount++; + }); + return 0; + } +}