Unverified Commit 89907f6d authored by Polina Cherkasova's avatar Polina Cherkasova Committed by GitHub

Enable ChangeNotifier clients to dispatch event of object creation in constructor. (#133060)

parent 382ceb57
...@@ -207,6 +207,39 @@ mixin class ChangeNotifier implements Listenable { ...@@ -207,6 +207,39 @@ mixin class ChangeNotifier implements Listenable {
@protected @protected
bool get hasListeners => _count > 0; bool get hasListeners => _count > 0;
/// Dispatches event of object creation to [MemoryAllocations.instance].
///
/// If the event was already dispatched or [kFlutterMemoryAllocationsEnabled]
/// is false, the method is noop.
///
/// Tools like leak_tracker use the event of object creation to help
/// developers identify the owner of the object, for troubleshooting purposes,
/// by taking stack trace at the moment of the event.
///
/// But, as [ChangeNotifier] is mixin, it does not have its own constructor. So, it
/// communicates object creation in first `addListener`, that results
/// in the stack trace pointing to `addListener`, not to constructor.
///
/// To make debugging easier, invoke [ChangeNotifier.maybeDispatchObjectCreation]
/// in constructor of the class. It will help
/// to identify the owner.
///
/// Make sure to invoke it with condition `if (kFlutterMemoryAllocationsEnabled) ...`
/// so that the method is tree-shaken away when the flag is false.
@protected
void maybeDispatchObjectCreation() {
// Tree shaker does not include this method and the class MemoryAllocations
// if kFlutterMemoryAllocationsEnabled is false.
if (kFlutterMemoryAllocationsEnabled && !_creationDispatched) {
MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterFoundationLibrary,
className: '$ChangeNotifier',
object: this,
);
_creationDispatched = true;
}
}
/// Register a closure to be called when the object changes. /// Register a closure to be called when the object changes.
/// ///
/// If the given closure is already registered, an additional instance is /// If the given closure is already registered, an additional instance is
...@@ -236,14 +269,11 @@ mixin class ChangeNotifier implements Listenable { ...@@ -236,14 +269,11 @@ mixin class ChangeNotifier implements Listenable {
@override @override
void addListener(VoidCallback listener) { void addListener(VoidCallback listener) {
assert(ChangeNotifier.debugAssertNotDisposed(this)); assert(ChangeNotifier.debugAssertNotDisposed(this));
if (kFlutterMemoryAllocationsEnabled && !_creationDispatched) {
MemoryAllocations.instance.dispatchObjectCreated( if (kFlutterMemoryAllocationsEnabled) {
library: _flutterFoundationLibrary, maybeDispatchObjectCreation();
className: '$ChangeNotifier',
object: this,
);
_creationDispatched = true;
} }
if (_count == _listeners.length) { if (_count == _listeners.length) {
if (_count == 0) { if (_count == 0) {
_listeners = List<VoidCallback?>.filled(1, null); _listeners = List<VoidCallback?>.filled(1, null);
...@@ -505,13 +535,8 @@ class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> { ...@@ -505,13 +535,8 @@ class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
/// Creates a [ChangeNotifier] that wraps this value. /// Creates a [ChangeNotifier] that wraps this value.
ValueNotifier(this._value) { ValueNotifier(this._value) {
if (kFlutterMemoryAllocationsEnabled) { if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectCreated( maybeDispatchObjectCreation();
library: _flutterFoundationLibrary,
className: '$ValueNotifier',
object: this,
);
} }
_creationDispatched = true;
} }
/// The current value stored in this notifier. /// The current value stored in this notifier.
......
...@@ -1196,6 +1196,13 @@ class ShortcutRegistryEntry { ...@@ -1196,6 +1196,13 @@ class ShortcutRegistryEntry {
/// widgets that are not descendants of the registry can listen to it (e.g. in /// widgets that are not descendants of the registry can listen to it (e.g. in
/// overlays). /// overlays).
class ShortcutRegistry with ChangeNotifier { class ShortcutRegistry with ChangeNotifier {
/// Creates an instance of [ShortcutRegistry].
ShortcutRegistry() {
if (kFlutterMemoryAllocationsEnabled) {
maybeDispatchObjectCreation();
}
}
bool _notificationScheduled = false; bool _notificationScheduled = false;
bool _disposed = false; bool _disposed = false;
......
...@@ -482,4 +482,8 @@ class _DefaultSnapshotPainter implements SnapshotPainter { ...@@ -482,4 +482,8 @@ class _DefaultSnapshotPainter implements SnapshotPainter {
@override @override
bool shouldRepaint(covariant _DefaultSnapshotPainter oldPainter) => false; bool shouldRepaint(covariant _DefaultSnapshotPainter oldPainter) => false;
@override
@protected
void maybeDispatchObjectCreation() { }
} }
...@@ -1852,6 +1852,22 @@ void main() { ...@@ -1852,6 +1852,22 @@ void main() {
}, throwsAssertionError); }, throwsAssertionError);
token.dispose(); token.dispose();
}); });
testWidgets('dispatches object creation in constructor', (WidgetTester tester) async {
final MemoryAllocations ma = MemoryAllocations.instance;
assert(!ma.hasListeners);
int eventCount = 0;
void listener(ObjectEvent event) => eventCount++;
ma.addListener(listener);
final ShortcutRegistry registry = ShortcutRegistry();
expect(eventCount, 1);
registry.dispose();
ma.removeListener(listener);
assert(!ma.hasListeners);
});
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment