Skip to content

Commit 16b7348

Browse files
authored
Switch debugAssertNotDisposed to be a static (flutter#104772)
This reverts part of the change made in flutter#103456 to expose a debug check for subclasses of ChangeNotifier to avoid code duplication. Instead of making debugAssertNotDisposed a public instance function, it is now a public static function. It makes it harder to call, slightly, but it means that everyone who implemented ChangeNotifier instead of extending it doesn't get broken.
1 parent b625353 commit 16b7348

File tree

4 files changed

+44
-18
lines changed

4 files changed

+44
-18
lines changed

packages/flutter/lib/src/foundation/change_notifier.dart

+13-10
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,22 @@ class ChangeNotifier implements Listenable {
129129
/// ```dart
130130
/// class MyNotifier with ChangeNotifier {
131131
/// void doUpdate() {
132-
/// assert(debugAssertNotDisposed());
132+
/// assert(ChangeNotifier.debugAssertNotDisposed(this));
133133
/// // ...
134134
/// }
135135
/// }
136136
/// ```
137137
/// {@end-tool}
138-
@protected
139-
bool debugAssertNotDisposed() {
138+
// This is static and not an instance method because too many people try to
139+
// implement ChangeNotifier instead of extending it (and so it is too breaking
140+
// to add a method, especially for debug).
141+
static bool debugAssertNotDisposed(ChangeNotifier notifier) {
140142
assert(() {
141-
if (_debugDisposed) {
143+
if (notifier._debugDisposed) {
142144
throw FlutterError(
143-
'A $runtimeType was used after being disposed.\n'
144-
'Once you have called dispose() on a $runtimeType, it can no longer be used.',
145+
'A ${notifier.runtimeType} was used after being disposed.\n'
146+
'Once you have called dispose() on a ${notifier.runtimeType}, it '
147+
'can no longer be used.',
145148
);
146149
}
147150
return true;
@@ -166,7 +169,7 @@ class ChangeNotifier implements Listenable {
166169
/// so, stopping that same work.
167170
@protected
168171
bool get hasListeners {
169-
assert(debugAssertNotDisposed());
172+
assert(ChangeNotifier.debugAssertNotDisposed(this));
170173
return _count > 0;
171174
}
172175

@@ -198,7 +201,7 @@ class ChangeNotifier implements Listenable {
198201
/// the list of closures that are notified when the object changes.
199202
@override
200203
void addListener(VoidCallback listener) {
201-
assert(debugAssertNotDisposed());
204+
assert(ChangeNotifier.debugAssertNotDisposed(this));
202205
if (_count == _listeners.length) {
203206
if (_count == 0) {
204207
_listeners = List<VoidCallback?>.filled(1, null);
@@ -293,7 +296,7 @@ class ChangeNotifier implements Listenable {
293296
/// This method should only be called by the object's owner.
294297
@mustCallSuper
295298
void dispose() {
296-
assert(debugAssertNotDisposed());
299+
assert(ChangeNotifier.debugAssertNotDisposed(this));
297300
assert(() {
298301
_debugDisposed = true;
299302
return true;
@@ -321,7 +324,7 @@ class ChangeNotifier implements Listenable {
321324
@visibleForTesting
322325
@pragma('vm:notify-debugger-on-exception')
323326
void notifyListeners() {
324-
assert(debugAssertNotDisposed());
327+
assert(ChangeNotifier.debugAssertNotDisposed(this));
325328
if (_count == 0) {
326329
return;
327330
}

packages/flutter/lib/src/widgets/restoration.dart

+5-5
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
473473

474474
@override
475475
void dispose() {
476-
assert(debugAssertNotDisposed()); // FYI, This uses ChangeNotifier's _debugDisposed, not _disposed.
476+
assert(ChangeNotifier.debugAssertNotDisposed(this)); // FYI, This uses ChangeNotifier's _debugDisposed, not _disposed.
477477
_owner?._unregister(this);
478478
super.dispose();
479479
_disposed = true;
@@ -483,14 +483,14 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
483483
String? _restorationId;
484484
RestorationMixin? _owner;
485485
void _register(String restorationId, RestorationMixin owner) {
486-
assert(debugAssertNotDisposed());
486+
assert(ChangeNotifier.debugAssertNotDisposed(this));
487487
assert(restorationId != null);
488488
assert(owner != null);
489489
_restorationId = restorationId;
490490
_owner = owner;
491491
}
492492
void _unregister() {
493-
assert(debugAssertNotDisposed());
493+
assert(ChangeNotifier.debugAssertNotDisposed(this));
494494
assert(_restorationId != null);
495495
assert(_owner != null);
496496
_restorationId = null;
@@ -503,14 +503,14 @@ abstract class RestorableProperty<T> extends ChangeNotifier {
503503
@protected
504504
State get state {
505505
assert(isRegistered);
506-
assert(debugAssertNotDisposed());
506+
assert(ChangeNotifier.debugAssertNotDisposed(this));
507507
return _owner!;
508508
}
509509

510510
/// Whether this property is currently registered with a [RestorationMixin].
511511
@protected
512512
bool get isRegistered {
513-
assert(debugAssertNotDisposed());
513+
assert(ChangeNotifier.debugAssertNotDisposed(this));
514514
return _restorationId != null;
515515
}
516516
}

packages/flutter/lib/src/widgets/shortcuts.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,7 @@ class ShortcutRegistry with ChangeNotifier {
10921092
///
10931093
/// Returns a copy: modifying the returned map will have no effect.
10941094
Map<ShortcutActivator, Intent> get shortcuts {
1095-
assert(debugAssertNotDisposed());
1095+
assert(ChangeNotifier.debugAssertNotDisposed(this));
10961096
return <ShortcutActivator, Intent>{
10971097
for (final MapEntry<ShortcutRegistryEntry, Map<ShortcutActivator, Intent>> entry in _tokenShortcuts.entries)
10981098
...entry.value,
@@ -1123,7 +1123,7 @@ class ShortcutRegistry with ChangeNotifier {
11231123
/// * [ShortcutRegistryEntry.dispose], a function used to remove the set of
11241124
/// shortcuts associated with a particular entry.
11251125
ShortcutRegistryEntry addAll(Map<ShortcutActivator, Intent> value) {
1126-
assert(debugAssertNotDisposed());
1126+
assert(ChangeNotifier.debugAssertNotDisposed(this));
11271127
final ShortcutRegistryEntry entry = ShortcutRegistryEntry._(this);
11281128
_tokenShortcuts[entry] = value;
11291129
assert(_debugCheckForDuplicates());
@@ -1191,7 +1191,7 @@ class ShortcutRegistry with ChangeNotifier {
11911191
// Replaces all the shortcuts associated with the given entry from this
11921192
// registry.
11931193
void _replaceAll(ShortcutRegistryEntry entry, Map<ShortcutActivator, Intent> value) {
1194-
assert(debugAssertNotDisposed());
1194+
assert(ChangeNotifier.debugAssertNotDisposed(this));
11951195
assert(_debugCheckTokenIsValid(entry));
11961196
_tokenShortcuts[entry] = value;
11971197
assert(_debugCheckForDuplicates());

packages/flutter/test/foundation/change_notifier_test.dart

+23
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,29 @@ void main() {
475475
);
476476
});
477477

478+
test('Calling debugAssertNotDisposed works as intended', () {
479+
final TestNotifier testNotifier = TestNotifier();
480+
expect(ChangeNotifier.debugAssertNotDisposed(testNotifier), isTrue);
481+
testNotifier.dispose();
482+
FlutterError? error;
483+
try {
484+
ChangeNotifier.debugAssertNotDisposed(testNotifier);
485+
} on FlutterError catch (e) {
486+
error = e;
487+
}
488+
expect(error, isNotNull);
489+
expect(error, isFlutterError);
490+
expect(
491+
error!.toStringDeep(),
492+
equalsIgnoringHashCodes(
493+
'FlutterError\n'
494+
' A TestNotifier was used after being disposed.\n'
495+
' Once you have called dispose() on a TestNotifier, it can no\n'
496+
' longer be used.\n',
497+
),
498+
);
499+
});
500+
478501
test('notifyListener can be called recursively', () {
479502
final Counter counter = Counter();
480503
final List<String> log = <String>[];

0 commit comments

Comments
 (0)