Skip to content

Commit 1348d8d

Browse files
committed
Optimistically remove listeners using Finalizer (#5)
1 parent 17d2a05 commit 1348d8d

File tree

3 files changed

+28
-2
lines changed

3 files changed

+28
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 2.0.1
2+
3+
Optimistically remove listeners when the `Element` they are listening to is garbage collected, reducing memory pressure. (#5)
4+
15
### 2.0.0
26

37
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)

lib/src/reactive_value_notifier.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ class ReactiveValueNotifier<T> extends ValueNotifier<T> {
1313
ReactiveValueNotifier(super.value);
1414

1515
/// An Expando mapping from `Element` objects to `true`, if the `Element`
16-
/// is subscribed to this `ValueNotifier`.
16+
/// is subscribed to this `ValueNotifier`. Used to ensure that an `Element`
17+
/// is only subscribed once.
1718
final _subscribedElements = Expando<bool>();
1819

20+
/// A [Finalizer] that will be called when an [Element] is garbage collected.
21+
/// Used to optimistically remove the listener corresponding to that
22+
/// [Element]. (The listener will be removed anyway when the [ValueNotifier]
23+
/// is disposed, or when the [ValueNotifier]'s value changes after the
24+
/// [Element] has been garbage collected, but this reduces memory pressure.)
25+
final Finalizer<void Function()> _finalizer = Finalizer((fn) => fn());
26+
1927
/// Fetch the [value] of this [ValueNotifier], and subscribe the element
2028
/// that is currently being built (the [context]) to any changes in the
2129
/// value.
@@ -55,13 +63,27 @@ class ReactiveValueNotifier<T> extends ValueNotifier<T> {
5563
}
5664
// Remove the element from the Expando of subscribed elements
5765
_subscribedElements[elementRefTarget] = null;
66+
67+
// Tell the finalizer it no longer needs to watch the element, since
68+
// we're about to manually remove the listener
69+
_finalizer.detach(elementRefTarget);
5870
}
71+
5972
// Remove the listener -- only listen to one change per build
6073
// (each subsequent build will resubscribe)
6174
removeListener(listenerWrapper.listener!);
6275
};
76+
6377
// Listen to changes to the ReactiveValue
6478
addListener(listenerWrapper.listener!);
79+
80+
// Tell the finalizer to remove the listener when the element is garbage
81+
// collected (this just reduces memory pressure, since the listener is
82+
// no longer needed, but even without this, when the listener is next
83+
// called, it would remove itself)
84+
_finalizer.attach(element, () => removeListener(listenerWrapper.listener!),
85+
detach: element);
86+
6587
// Return the current value
6688
return value;
6789
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_reactive_value
22
description: Simple reactive state management for Flutter.
3-
version: 2.0.0
3+
version: 2.0.1
44
homepage: https://github.com/lukehutch/flutter_reactive_value
55

66
environment:

0 commit comments

Comments
 (0)