Skip to content

Commit

Permalink
Optimistically remove listeners using Finalizer (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukehutch committed Dec 14, 2023
1 parent 17d2a05 commit 1348d8d
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
24 changes: 23 additions & 1 deletion lib/src/reactive_value_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ class ReactiveValueNotifier<T> extends ValueNotifier<T> {
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<bool>();

/// 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<void Function()> _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.
Expand Down Expand Up @@ -55,13 +63,27 @@ class ReactiveValueNotifier<T> extends ValueNotifier<T> {
}
// 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;
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down

0 comments on commit 1348d8d

Please sign in to comment.