Skip to content

Commit

Permalink
Fix memory leak (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukehutch committed Dec 13, 2023
1 parent a7afb77 commit bf802cd
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 20 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.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)

### 1.0.4

Specify correct minimum Dart and Flutter versions (fixes #2).
Expand Down
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This library provides a mechanism for causing a UI to reactively update in respo

This is the simplest possible state management / reactive UI update solution for Flutter, by far, reducing boilerplate compared to all the other insanely complex [state management approaches](https://docs.flutter.dev/development/data-and-backend/state-mgmt/options) currently available.

The closest thing to `flutter_reactive_value` is [`ValueListenableBuilder`](https://api.flutter.dev/flutter/widgets/ValueListenableBuilder-class.html). `flutter_reactive_value` provides the same basic capabilities as `ValueListenableBuilder`, but with much less syntactic overhead (i.e. you could think of `flutter_reactive_value` as syntactic sugar). `ValueListenableBuilder` may work better if your reactive widget has a child element that is complex and non-reactive.
The closest thing to `flutter_reactive_value` is [`ValueListenableBuilder`](https://api.flutter.dev/flutter/widgets/ValueListenableBuilder-class.html). `flutter_reactive_value` provides the same basic capabilities as `ValueListenableBuilder`, but with much less syntactic overhead (i.e. you could think of `flutter_reactive_value` as syntactic sugar). `ValueListenableBuilder` may work better if your reactive widget has a child element that is complex and non-reactive, because it takes a `child` parameter for any child widget that is not affected by changes to the `ValueNotifier`'s value.

## Usage

Expand All @@ -23,13 +23,13 @@ dependencies:
import 'package:flutter_reactive_value/flutter_reactive_value.dart'
```

(3) Use the standard Flutter [`ValueNotifier<T>`](https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html) mechanism to declare any values you want your UI to listen for changes to:
(3) Use `ReactiveValueNotifier<T>` rather than the standard Flutter [`ValueNotifier<T>`](https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html) to declare any values you want your UI to listen for changes to:

```dart
final counter = ValueNotifier(0);
final counter = ReactiveValueNotifier(0);
```

(4) Build your UI the standard way, using a `Widget` hierarchy, and anywhere you want to use the value and respond to future changes in the value by updating the UI, instead of using the `ValueNotifier.value` getter method, use `ValueNotifier.reactiveValue(BuildContext)`:
(4) Build your UI the standard way, using a `Widget` hierarchy, and anywhere you want to use the value and respond to future changes in the value by updating the UI, instead of using the usual `ValueNotifier.value` getter method, use `ReactiveValueNotifier.reactiveValue(BuildContext)`:

```dart
class HomeView extends StatelessWidget {
Expand Down Expand Up @@ -97,7 +97,7 @@ Container(
If you try to wrap a collection or object in a `ValueNotifier`, e.g. to track a set of values using `ValueNotifier<Set<T>>`, then modifying fields, or adding or removing values from the collection or object will not notify the listeners of the `ValueNotifier` that the value has changed (because the value itself has not changed). In this case you can call the extension method `notifyChanged()` to manually call the listeners. For example:

```dart
final tags = ValueNotifier(<String>{});
final tags = ReactiveValueNotifier(<String>{});
void addOrRemoveTag(String tag, bool add) {
if ((add && tags.value.add(tag)) || (!add && tags.value.remove(tag))) {
Expand All @@ -110,10 +110,6 @@ void addOrRemoveTag(String tag, bool add) {

Use `flutter_reactive_value` together with my other library, [`flutter_persistent_value_notifier`](https://github.com/lukehutch/flutter_persistent_value_notifier), to enable persistent reactive state in your app!

## The code

The `flutter_reactive_value` mechanism is extremely simple. [See the code here.](https://github.com/lukehutch/flutter_reactive_value/blob/main/lib/src/reactive_value_notifier.dart)

## Author

`flutter_reactive_value` was written by Luke Hutchison, and is released under the MIT license.
4 changes: 2 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flutter_reactive_value/flutter_reactive_value.dart';
import 'package:flutter/material.dart';
import 'package:flutter_reactive_value/flutter_reactive_value.dart';

void main() async {
runApp(
Expand All @@ -10,7 +10,7 @@ void main() async {
);
}

final counter = ValueNotifier<int>(0);
final counter = ReactiveValueNotifier<int>(0);

class HomeView extends StatelessWidget {
const HomeView({Key? key}) : super(key: key);
Expand Down
43 changes: 35 additions & 8 deletions lib/src/reactive_value_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,60 @@ class _ListenerWrapper {

/// Extend [ValueNotifier] so that [Element] objects in the build tree can
/// respond to changes in the value.
extension ReactiveValueNotifier<T> on ValueNotifier<T> {
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`.
final _subscribedElements = Expando<bool>();

/// Fetch the [value] of this [ValueNotifier], and subscribe the element
/// that is currently being built (the [context]) to any changes in the
/// value.
T reactiveValue(BuildContext context) {
final elementRef = WeakReference(context as Element);
final element = context as Element;
// If element is already subscribed, return the current value without
// subscribing again
if (_subscribedElements[element] == true) {
return value;
}
// Add a weak reference to the element to the Expando of subscribed
// elements
_subscribedElements[element] = true;

// Create a weak reference to the element
final elementRef = WeakReference(element);

final listenerWrapper = _ListenerWrapper();
listenerWrapper.listener = () {
// State is not supposed to be mutated during `build`
assert(
SchedulerBinding.instance.schedulerPhase !=
SchedulerPhase.persistentCallbacks,
'Do not mutate state (by setting the value of the ValueNotifier '
'that you are subscribed to) during a `build` method. If you need '
'to schedule a value update after `build` has completed, use '
'`SchedulerBinding.instance.scheduleTask(updateTask, Priority.idle)`, '
'`SchedulerBinding.addPostFrameCallback(updateTask)`, '
'SchedulerBinding.instance.scheduleTask(updateTask, Priority.idle), '
'SchedulerBinding.addPostFrameCallback(updateTask), '
'or similar.');
// If the element has not been garbage collected (causing
// `elementRef.target` to be null), or unmounted
if (elementRef.target?.mounted ?? false) {
// Mark the element as needing to be rebuilt
elementRef.target!.markNeedsBuild();
final elementRefTarget = elementRef.target;
if (elementRefTarget != null) {
if (elementRefTarget.mounted) {
// Then mark the element as needing to be rebuilt
elementRefTarget.markNeedsBuild();
}
// Remove the element from the Expando of subscribed elements
_subscribedElements[elementRefTarget] = null;
}
// Remove the listener -- only listen to one change per `build`
// 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!);
// 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: 1.0.4
version: 2.0.0
homepage: https://github.com/lukehutch/flutter_reactive_value

environment:
Expand Down

0 comments on commit bf802cd

Please sign in to comment.