Skip to content

Commit 299de51

Browse files
authored
Added disposeNotifier flag (#4707)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added `disposeNotifier` parameter to ChangeNotifierProvider (defaults to `true`). Set to `false` to prevent automatic disposal of notifiers, enabling shared ChangeNotifiers across multiple providers and simplifying migration paths from provider-based architectures. * **Tests** * Added test coverage for the new disposal control functionality. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent e35c43c commit 299de51

4 files changed

Lines changed: 106 additions & 3 deletions

File tree

packages/flutter_riverpod/CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Unreleased minor
2+
3+
- Added `ChangeNotifierProvider(disposeNotifier: false)`, to disable the automatic
4+
disposal of the created `ChangeNotifier`. This enables simpler migration from `pkg:provider` to `pkg:riverpod`
5+
when a `ChangeNotifier` is reused between multiple providers.
6+
17
## 3.2.1 - 2026-02-03
28

39
- Fixed a bug where resuming a paused provider could cause it to never
@@ -1505,4 +1511,3 @@ The behavior is the same. Only the syntax changed.
15051511
Initial release
15061512

15071513
<!-- cSpell:ignoreRegExp @\w+ -->
1508-

packages/flutter_riverpod/lib/src/builders.dart

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/flutter_riverpod/lib/src/providers/legacy/change_notifier_provider.dart

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ final class ChangeNotifierProvider<NotifierT extends ChangeNotifier?>
7979
super.dependencies,
8080
super.isAutoDispose = false,
8181
super.retry,
82+
this.disposeNotifier = true,
8283
}) : super(
8384
$allTransitiveDependencies: computeAllTransitiveDependencies(
8485
dependencies,
@@ -99,6 +100,7 @@ final class ChangeNotifierProvider<NotifierT extends ChangeNotifier?>
99100
super.from,
100101
super.argument,
101102
super.retry,
103+
required this.disposeNotifier,
102104
});
103105

104106
/// {@macro riverpod.autoDispose}
@@ -107,6 +109,14 @@ final class ChangeNotifierProvider<NotifierT extends ChangeNotifier?>
107109
/// {@macro riverpod.family}
108110
static const family = ChangeNotifierProviderFamilyBuilder();
109111

112+
/// Whether to automatically call [ChangeNotifier.dispose] on the created [ChangeNotifier].
113+
///
114+
/// This is `true` by default.
115+
///
116+
/// Disabling this may be useful if you want to share a [ChangeNotifier]
117+
/// across multiple providers.
118+
final bool disposeNotifier;
119+
110120
/// Obtains the [ChangeNotifier] associated with this provider, without listening
111121
/// to state changes.
112122
///
@@ -141,6 +151,43 @@ final class ChangeNotifierProvider<NotifierT extends ChangeNotifier?>
141151
) {
142152
return _ChangeNotifierProviderElement<NotifierT>._(pointer);
143153
}
154+
155+
@override
156+
ChangeNotifierProvider<NotifierT> $view({required Create<NotifierT> create}) {
157+
return _View<NotifierT>(this, create);
158+
}
159+
}
160+
161+
final class _View<NotifierT extends ChangeNotifier?>
162+
extends ChangeNotifierProvider<NotifierT> {
163+
/// Implementation detail of `riverpod_generator`.
164+
/// Do not use, as this can be removed at any time.
165+
_View(this._inner, Create<NotifierT> _createOverride)
166+
: super.internal(
167+
_createOverride,
168+
name: _inner.name,
169+
from: _inner.from,
170+
argument: _inner.argument,
171+
dependencies: _inner.dependencies,
172+
$allTransitiveDependencies: _inner.$allTransitiveDependencies,
173+
isAutoDispose: _inner.isAutoDispose,
174+
retry: _inner.retry,
175+
disposeNotifier: _inner.disposeNotifier,
176+
);
177+
178+
final ChangeNotifierProvider<NotifierT> _inner;
179+
180+
/// @nodoc
181+
@internal
182+
@override
183+
_ChangeNotifierProviderElement<NotifierT> $createElement(
184+
$ProviderPointer pointer,
185+
) {
186+
return _inner.$createElement(pointer)..provider = this;
187+
}
188+
189+
@override
190+
String? debugGetCreateSourceHash() => _inner.debugGetCreateSourceHash();
144191
}
145192

146193
/// The element of [ChangeNotifierProvider].
@@ -179,7 +226,8 @@ class _ChangeNotifierProviderElement<NotifierT extends ChangeNotifier?>
179226
_removeListener = null;
180227

181228
final notifier = _notifierNotifier.result?.value;
182-
if (notifier != null) {
229+
if (notifier != null &&
230+
(provider as ChangeNotifierProvider).disposeNotifier) {
183231
container.runGuarded(notifier.dispose);
184232
}
185233
_notifierNotifier.result = null;
@@ -215,8 +263,30 @@ final class ChangeNotifierProviderFamily<
215263
super.dependencies,
216264
super.isAutoDispose = false,
217265
super.retry,
266+
bool disposeNotifier = true,
218267
}) : super(
219-
providerFactory: ChangeNotifierProvider.internal,
268+
providerFactory: (
269+
create, {
270+
required $allTransitiveDependencies,
271+
required argument,
272+
required dependencies,
273+
required from,
274+
required isAutoDispose,
275+
required name,
276+
required retry,
277+
}) {
278+
return ChangeNotifierProvider<NotifierT>.internal(
279+
create,
280+
disposeNotifier: disposeNotifier,
281+
name: name,
282+
dependencies: dependencies,
283+
$allTransitiveDependencies: $allTransitiveDependencies,
284+
isAutoDispose: isAutoDispose,
285+
argument: argument,
286+
from: from,
287+
retry: retry,
288+
);
289+
},
220290
$allTransitiveDependencies: computeAllTransitiveDependencies(
221291
dependencies,
222292
),

packages/flutter_riverpod/test/providers/change_notifier/change_notifier_provider_test.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,36 @@ import 'dart:async';
55
import 'package:flutter/widgets.dart' hide Listener;
66
import 'package:flutter_riverpod/src/internals.dart';
77
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:mockito/mockito.dart';
89

10+
import '../../provider_test.dart';
911
import '../../utils.dart';
1012

1113
void main() {
14+
test('Can specify disposeNotifier', () {
15+
final onDispose = OnDisposeMock();
16+
final notifier = DelegateNotifier(onDispose: onDispose.call);
17+
final onDispose2 = OnDisposeMock();
18+
final notifier2 = DelegateNotifier(onDispose: onDispose.call);
19+
final container = ProviderContainer.test();
20+
final provider = ChangeNotifierProvider(
21+
(_) => notifier,
22+
disposeNotifier: false,
23+
);
24+
final family = ChangeNotifierProvider.family(
25+
(ref, int arg) => notifier2,
26+
disposeNotifier: false,
27+
);
28+
29+
container.read(provider);
30+
container.refresh(provider);
31+
verifyNever(onDispose.call());
32+
33+
container.read(family(0));
34+
container.refresh(family(0));
35+
verifyNever(onDispose2.call());
36+
});
37+
1238
test('Guards ChangeNotifier.dispose', () {
1339
final notifier = DelegateNotifier(
1440
onDispose: () => throw StateError('called'),

0 commit comments

Comments
 (0)