diff --git a/CHANGELOG.md b/CHANGELOG.md index e29d424..5c1812c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 2.0.1 + +Optimistically remove listeners when the `Element` they are listening to is garbage collected, reducing memory pressure. (#5) + ### 2.0.0 Fix memory leak (listeners were being added with every build). This required converting `ReactiveValueNotifier` from an extension to a subclass, because it needed an extra private field. (#5) diff --git a/lib/src/reactive_value_notifier.dart b/lib/src/reactive_value_notifier.dart index 11d7e25..7b0aa4e 100644 --- a/lib/src/reactive_value_notifier.dart +++ b/lib/src/reactive_value_notifier.dart @@ -13,9 +13,17 @@ class ReactiveValueNotifier extends ValueNotifier { ReactiveValueNotifier(super.value); /// An Expando mapping from `Element` objects to `true`, if the `Element` - /// is subscribed to this `ValueNotifier`. + /// is subscribed to this `ValueNotifier`. Used to ensure that an `Element` + /// is only subscribed once. final _subscribedElements = Expando(); + /// A [Finalizer] that will be called when an [Element] is garbage collected. + /// Used to optimistically remove the listener corresponding to that + /// [Element]. (The listener will be removed anyway when the [ValueNotifier] + /// is disposed, or when the [ValueNotifier]'s value changes after the + /// [Element] has been garbage collected, but this reduces memory pressure.) + final Finalizer _finalizer = Finalizer((fn) => fn()); + /// Fetch the [value] of this [ValueNotifier], and subscribe the element /// that is currently being built (the [context]) to any changes in the /// value. @@ -55,13 +63,27 @@ class ReactiveValueNotifier extends ValueNotifier { } // Remove the element from the Expando of subscribed elements _subscribedElements[elementRefTarget] = null; + + // Tell the finalizer it no longer needs to watch the element, since + // we're about to manually remove the listener + _finalizer.detach(elementRefTarget); } + // Remove the listener -- only listen to one change per build // (each subsequent build will resubscribe) removeListener(listenerWrapper.listener!); }; + // Listen to changes to the ReactiveValue addListener(listenerWrapper.listener!); + + // Tell the finalizer to remove the listener when the element is garbage + // collected (this just reduces memory pressure, since the listener is + // no longer needed, but even without this, when the listener is next + // called, it would remove itself) + _finalizer.attach(element, () => removeListener(listenerWrapper.listener!), + detach: element); + // Return the current value return value; } diff --git a/pubspec.yaml b/pubspec.yaml index d919ca6..2542ab3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_reactive_value description: Simple reactive state management for Flutter. -version: 2.0.0 +version: 2.0.1 homepage: https://github.com/lukehutch/flutter_reactive_value environment: