From caff4998fe407993e6feea936a9278af1b008eb4 Mon Sep 17 00:00:00 2001 From: jinyus Date: Sun, 7 Jan 2024 11:12:24 -0500 Subject: [PATCH] add support to configure conditional listening --- state_beacon/CHANGELOG.md | 4 +++ state_beacon/lib/src/effect.dart | 9 ++--- state_beacon/lib/src/state_beacon.dart | 47 +++++++++++++++++++------- state_beacon/pubspec.yaml | 2 +- state_beacon/test/src/effect_test.dart | 34 +++++++++++++++++++ 5 files changed, 79 insertions(+), 17 deletions(-) diff --git a/state_beacon/CHANGELOG.md b/state_beacon/CHANGELOG.md index 49ca5620..9dd5a9f5 100644 --- a/state_beacon/CHANGELOG.md +++ b/state_beacon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.17.1 + +- Make conditional listening configurable for `Beacon.createEffect` ,`Beacon.derived` and `Beacon.derivedFuture` + ## 0.17.0 - Mdd debugLabel to beacons diff --git a/state_beacon/lib/src/effect.dart b/state_beacon/lib/src/effect.dart index 82736ac3..7fc17d81 100644 --- a/state_beacon/lib/src/effect.dart +++ b/state_beacon/lib/src/effect.dart @@ -10,12 +10,13 @@ final Set _listenersToPingAfterBatchJob = {}; class _Effect { final Set dependencies; late final EffectClosure func; + final bool _supportConditional; - _Effect() : dependencies = {}; + _Effect(this._supportConditional) : dependencies = {}; VoidCallback execute(Function fn) { func = EffectClosure(() { - _cleanup(this); + if (_supportConditional) _cleanup(this); _effectStack.add(this); try { fn(); @@ -41,8 +42,8 @@ class _Effect { } } -VoidCallback effect(Function fn) { - final effect = _Effect(); +VoidCallback effect(Function fn, {bool supportConditional = true}) { + final effect = _Effect(supportConditional); return effect.execute(fn); } diff --git a/state_beacon/lib/src/state_beacon.dart b/state_beacon/lib/src/state_beacon.dart index 5ea1f96e..f1f313bc 100644 --- a/state_beacon/lib/src/state_beacon.dart +++ b/state_beacon/lib/src/state_beacon.dart @@ -361,6 +361,10 @@ abstract class Beacon { /// /// If `manualStart` is `true`, the future will not execute until [start()] is called. /// + /// If `supportConditional` is `true`, the effect look for its dependencies on its first run. + /// This means once a beacon is added as a dependency, it will not be removed even if it's no longer used. + /// Defaults to `true`. + /// /// Example: /// ```dart /// final age = Beacon.writable(18); @@ -376,16 +380,20 @@ abstract class Beacon { T Function() compute, { bool manualStart = false, String? debugLabel, + bool supportConditional = true, }) { final beacon = DerivedBeacon(manualStart: manualStart) ..setDebugLabel(debugLabel ?? 'DerivedBeacon<$T>'); - final unsub = effect(() { - // beacon is manually triggered if in idle state - if (beacon.status.value == DerivedStatus.idle) return; + final unsub = effect( + () { + // beacon is manually triggered if in idle state + if (beacon.status.value == DerivedStatus.idle) return; - beacon.forceSetValue(compute()); - }); + beacon.forceSetValue(compute()); + }, + supportConditional: supportConditional, + ); beacon.$setInternalEffectUnsubscriber(unsub); @@ -401,6 +409,10 @@ abstract class Beacon { /// If `cancelRunning` is `true`, the results of a current execution will be discarded /// if another execution is triggered before the current one finishes. /// + /// If `supportConditional` is `true`, the effect look for its dependencies on its first run. + /// This means once a beacon is added as a dependency, it will not be removed even if it's no longer used. + /// Defaults to `true`. + /// /// Example: /// ```dart /// final counter = Beacon.writable(0); @@ -430,6 +442,7 @@ abstract class Beacon { bool manualStart = false, bool cancelRunning = true, String? debugLabel, + bool supportConditional = true, }) { final beacon = DerivedFutureBeacon( compute, @@ -437,12 +450,15 @@ abstract class Beacon { cancelRunning: cancelRunning, )..setDebugLabel(debugLabel ?? 'DerivedFutureBeacon<$T>'); - final unsub = effect(() async { - // beacon is manually triggered if in idle state - if (beacon.status.value == DerivedFutureStatus.idle) return; + final unsub = effect( + () async { + // beacon is manually triggered if in idle state + if (beacon.status.value == DerivedFutureStatus.idle) return; - await beacon.run(); - }); + await beacon.run(); + }, + supportConditional: supportConditional, + ); beacon.$setInternalEffectUnsubscriber(unsub); @@ -493,6 +509,10 @@ abstract class Beacon { /// Creates an effect based on a provided function. The provided function will be called /// whenever one of its dependencies change. /// + /// If `supportConditional` is `true`, the effect look for its dependencies on its first run. + /// This means once a beacon is added as a dependency, it will not be removed even if it's no longer used. + /// Defaults to `true`. + /// /// Example: /// ```dart /// final age = Beacon.writable(15); @@ -509,8 +529,11 @@ abstract class Beacon { /// /// age.value = 20; // Outputs: "You can vote!" /// ``` - static VoidCallback createEffect(Function fn) { - return effect(fn); + static VoidCallback createEffect( + Function fn, { + bool supportConditional = true, + }) { + return effect(fn, supportConditional: supportConditional); } /// Executes a batched update which allows multiple updates to be batched into a single update. diff --git a/state_beacon/pubspec.yaml b/state_beacon/pubspec.yaml index 1e492ced..270163bf 100644 --- a/state_beacon/pubspec.yaml +++ b/state_beacon/pubspec.yaml @@ -1,6 +1,6 @@ name: state_beacon description: A reactive primitive and simple state managerment solution for dart and flutter -version: 0.17.0 +version: 0.17.1 repository: https://github.com/jinyus/dart_beacon environment: diff --git a/state_beacon/test/src/effect_test.dart b/state_beacon/test/src/effect_test.dart index aead89e2..7c6da7e2 100644 --- a/state_beacon/test/src/effect_test.dart +++ b/state_beacon/test/src/effect_test.dart @@ -34,6 +34,40 @@ void main() { expect(called, equals(6)); }); + test('should continue listening to unused beacons', () { + final name = Beacon.writable("Bob"); + final age = Beacon.writable(20); + final college = Beacon.writable("MIT"); + + var called = 0; + Beacon.createEffect( + () { + called++; + // ignore: unused_local_variable + var msg = '${name.value} is ${age.value} years old'; + + if (age.value > 21) { + msg += ' and can go to ${college.value}'; + } + + // print(msg); + }, + supportConditional: false, + ); + + name.value = "Alice"; + age.value = 21; + college.value = "Stanford"; + age.value = 22; + college.value = "Harvard"; + age.value = 18; + + // Should still listen to college beacon even if age is less than 21 + college.value = "Yale"; + + expect(called, equals(7)); + }); + test('should run when a dependency changes', () { var beacon = Beacon.writable(10); var effectCalled = false;