state_beacon_core
A reactive primitive and simple state managerment solution for dart.
3.1.1
3.3K downloads/mo
#4460 most downloaded on pub.dev
zupat/dart_beacon
What this package is like to depend on
Last release 3 months ago
21 May 2026
Ships unpredictably
gaps range from 8 days to 1.4 years
Nearly every release is documented
notes for 63 of 64 stable releases
Nothing withdrawn
no release was ever pulled
3 years old
64 releases · first in 2024
17 releases in the last 12 months
see the full history below
Release timeline
64 releases · Jan 2024 to May 2026Releases
latest 60 of 64-
3.1.121 May 2026Release notes
Open source →- [Feat] Add
asErrorgetter to theAsyncValue. This allows users to easily retrieve theAsyncErrorinstance when the state is an error, or null otherwise.
- [Feat] Add
-
3.1.002 Mar 2026Release notes
Open source →What's Changed
- refactor: improve edge-case status check logic in DerivedSubscription by @zupat in #173
- refactor: simplify status comparison in stale method for DerivedBeacon and DerivedSubscription by @zupat in #174
- Feat: Add ProgressBeacon by @zupat in #179
- Refactor/progress-beacon by @zupat in #180
- feat: add ProgressStatus enum and update ProgressBeacon status management by @zupat in #182
New Contributors
Full Changelog: v3.0.1...v3.1.0
Release notes
Open source →-
[Feat] Add
ProgressBeacon(Beacon.progress) with support for status tracking (ProgressStatus), manual control (start/stop/pause/resume), looping, andBeaconGroupintegration. -
[Refactor] Internal improvements for
DerivedSubscriptionandDerivedBeaconstatus propagation.
-
3.0.107 Jan 2026Release notes
Open source →What's Changed
- refactor error handling in FutureBeacon to use pattern matching by @zupat in #169
- fix: edgecase in derived sub with startNow=false by @zupat in #171
Full Changelog: v3.0.0...v3.0.1
Release notes
Open source →- [Fix] Bug where subscriptions to derived beacons with startNow=false would not run when the beacon was accessed after the subscription was created but before the next value was emitted.
-
3.0.030 Dec 2025Release notes
Open source →What's Changed
-
[Breaking]
anyBeacon.next()now throws if the beacon is disposed while waiting for the next value. This is a breaking change because previously it would complete with the current value if the beacon was disposed. -
[Feat] Add
anyBeacon.nextOrNull()method that returnsnullif the beacon is disposed while waiting for the next value.
Full Changelog: v2.0.3...v3.0.0
Release notes
Open source →-
[Breaking]
anyBeacon.next()now throws if the beacon is disposed while waiting for the next value. This is a breaking change because previously it would complete with the current value if the beacon was disposed. -
[Feat] Add
anyBeacon.nextOrNull()method that returnsnullif the beacon is disposed while waiting for the next value.
-
-
2.0.328 Dec 2025Release notes
Open source →What's Changed
- fix- dirty status calculation in derived which behaved wrong when the value is nullable by @JinyuS in #164
- V2.0.3-release by @JinyuS in #165
Full Changelog: v2.0.2...v2.0.3
-
2.0.220 Dec 2025Release notes
Open source →What's Changed
- Create special class for subscriptions on derived beacons by @JinyuS in #159
- V2.0.1-release by @JinyuS in #160
- Add BeaconGroup.textEditing by @JinyuS in #161
- add select extension methods to beaconcontroller by @JinyuS in #162
- V2.0.2-release by @JinyuS in #163
Full Changelog: v2.0.0...v2.0.2
Release notes
Open source →-[Fix] Nullable derived beacons didn't send notifications in some instances.
-
2.0.118 Dec 2025 -
2.0.017 Dec 2025Release notes
Open source →What's Changed
Breaking Changes
-
Remove synchronous subscriptions on DerivedBeacon: Only available for Writable and BufferedBeacons.
-
Chaining methods are now asynchronous: ie: map, filter, debounce, throttle, buffer, and bufferTime now operate asynchronously
-
Immutable chained beacons: The beacons returned from chaining methods are now immutable.
-
Stream access change: anyBeacon.toStream() is now anyBeacon.stream
-
Filter allowFirst parameter: The lazyBypass parameter in FilteredBeacon was replaced with allowFirst (set to false by default)
-
Debounce allowFirst parameter: Added allowFirst parameter to lazyDebounced and debounce chain method (set to false by default)
Full Changelog: https://github.com/jinyus/dart_beacon/blob/647fb0bdb664533fdefdfae106255652c6ad18b9/packages/state_beacon/CHANGELOG.md
Release notes
Open source →-
[Breaking]
anyBeacon.toStream()is nowanyBeacon.stream -
[Breaking] The lazyBypass parameter in
.filter()chain method was replaced with allowFirst (set to false by default).Previously, the first value sent to a lazy filtered beacon would not be filtered as
lazyBypasswastrueby default. This caused confusion as most persons expect it to be filtered. The name of the parameter has been changed toallowFirstand it is set tofalseby default.OLD
final count = Beacon.writable(0); final gtThan10 = count.filter((prev,next) => next>10); expect(gtThan10.peek(), 0); // first value was set immediatelyNEW
final count = Beacon.writable(0); final gtThan10 = count.filter((prev,next) => next>10); expect(gtThan10.peek, throwsException) // no value has been set as yet count.value = 20; await gtThan10.next(); expect(d.peek(), 20) // value set after passing the filter -
[Breaking] Added allowFirst parameter to lazyDebounced and
.debounce()chain method (set to false by default)Previously, the first value sent to a lazy debounced beacon would not be debounced. It is now debounced by default and you can allow the first value to go through by setting
allowFirsttotrue.OLD
final ms500 = Duration(milliseconds:500); final count = Beacon.writable(0); final d = count.debounce(ms500); expect(d.peek(), 0); // first value was set immediatelyNEW
final ms500 = Duration(milliseconds:500); final count = Beacon.writable(0); final d = count.debounce(ms500); expect(d.peek, throwsException) // no value has been set as yet await Future.delayed(ms500*2); expect(d.peek(), 0) // value set after being debounced -
[Breaking] synchronous parameter has been removed from the
.subscribe()method. Synchronous subscriptions are only available forWritableandBufferedbeacons through the.subscribeSynchronously()method. -
[Breaking] Chaining methods are now asynchronous and return immutable beacons. ie:
map,filter,debounce,throttle,buffer, andbufferTime.These being writable complicated the codebase as writes had to be rerouted to the first mutable beacon in the chain. The alternative is to mutate the original beacon directly.
-
-
1.3.413 Dec 2025Release notes
Open source →- [Fix] synchronous subscription RangeError when disposed in it's callback.
-
1.3.311 Dec 2025Release notes
Open source →What's Changed
- Deprecate supportConditional parameter in effect methods by @JinyuS in #146
- persist forced sets for throttled beacon by @JinyuS in #147
- fix: update remove method to notify only on successful removal by @JinyuS in #148
- fix: update remove method to notify only on successful removal by @JinyuS in #149
- Fix previousValue assignment logic and update tests by @JinyuS in #150
- Prevent immediate callback invocation for subscriptions with startNow=false by @JinyuS in #151
Full Changelog: v1.3.2...v1.3.3
Release notes
Open source →- [Deprecate] deprecate supportConditional parameter in effect methods. This param was already ignored in v0.34.0 but wasn't marked as deprecated
- [Fix] forced writes to throttled beacons incorrectly dropped the
forceflag when those writes were added to the buffer. - [Fix] Map.remove,List.remove and Set.remove no longer notifies listerners when nothing was removed.
- [Fix] previousValue was incorrectly set when using lazy beacons
- [Fix] Edge case where a subscription to a derived beacon with startNow=false would run immediately
-
1.3.206 Dec 2025Release notes
Open source →What's Changed
- Fix method chaining for derived sources by @JinyuS in #142
- V1.3.1-release by @JinyuS in #143
- Make sure dispose is only called once by @JinyuS in #145
Full Changelog: v1.3.0...v1.3.2
-
1.3.104 Dec 2025Release notes
Open source →- [Fix] Chaining methods on derived beacons now eagerly fetches the value allowing it to be used instantly.
final count = Beacon.writable<int>(0); final throttled = Beacon.derived(() => count.value * 2).throttle(k10ms); expect(throttled.value, 0); -
1.3.003 Dec 2025Release notes
Open source →- [Feat] Add queuing to FutureBeacon.updateWith()
The
updateWithmethod calls are now queued when there is an ongoing update. This ensures that all calls are executed in the order they were made, preventing race conditions and inconsistent state.
- [Feat] Add queuing to FutureBeacon.updateWith()
The
-
1.2.002 Dec 2025Release notes
Open source →- [Feat] Add
Future.updateWith()
The
updateWithmethod allows you to update a FutureBeacon's value with the provided callback. This differs fromoverrideWithbecause it updates the value only once, whileoverrideWithreplaces the original callback supplied to the beacon.Future<List<Todo>> loadTodos() async { ... } Future<List<Todo>> addTodo(Todo newTodo) async { await todoService.addTodo(newTodo); final currentTodos = todosBeacon.lastData ?? []; return [newTodo, ...currentTodos]; } final todosBeacon = Beacon.future(() => loadTodos()); // Later, add a new todo without refetching all todos await todosBeacon.updateWith(() => addTodo(newTodo)); // You can also provide an optimistic result // which will be set immediately while the future is being resolved. final optimisticTodos = [newTodo, ...todosBeacon.lastData ?? []]; await todosBeacon.updateWith( () => addTodo(newTodo), optimisticResult: optimisticTodos, ); - [Feat] Add
-
1.1.030 Nov 2025Release notes
Open source →- [Feat] Derived Beacons can now access their own value with '.peek()'. The beacon must have a value so a base case is required.
final counter = Beacon.writable(0); late final ReadableBeacon<int> accumulated; accumulated = Beacon.derived(() { final count = counter.value; if (count == 0) { return 0; // base case } return accumulated.peek() + counter.value; }); -
1.0.229 Nov 2025 -
1.0.110 Nov 2025Release notes
Open source →- Bug Fix: Flutter edge-case for derivedBeacons. This was fixed before but the current fix is more efficient.
-
1.0.030 May 2024 -
0.43.624 Apr 2024 -
0.43.503 Apr 2024 -
0.43.429 Mar 2024Release notes
Open source →- [Fix] Fix bug with
FutureBeacons not autosleeping - [Feat] Expose list of beacons created in a BeaconGroup wuth
BeaconGroup.beacons - [Feat] Add
BeaconGroup.onCreateto allow adding a callback to be run when a beacon is created
- [Fix] Fix bug with
-
0.43.324 Mar 2024Release notes
Open source →- [Fix] Rare bug in FutureBeacon when start is called multiple times synchronously.
-
0.43.224 Mar 2024 -
0.43.121 Mar 2024 -
0.43.015 Mar 2024Release notes
Open source →- [Breaking] The
beaconsgetter forBeacon.familyhas been replaced withentries. This is a breaking change because it returns aMapEntry<Key,Beacon>instead of aBeacon.
- [Breaking] The
-
0.42.113 Mar 2024 -
0.42.010 Mar 2024Release notes
Open source →- [Breaking]
resetIfErroroption fortoFuture()is nowtrueby default. This was done because there's rarely a case where you'd want it to throw instantly. If you want to keep the previous value, setresetIfErrortofalse.
- [Breaking]
-
0.41.208 Mar 2024 -
0.41.106 Mar 2024 -
0.41.006 Mar 2024Release notes
Open source →-
[Feat] Add
Beacon.periodicthat emits values periodically.final myBeacon = Beacon.periodic(Duration(seconds: 1), (i) => i + 1); final nextFive = await myBeacon.buffer(5).next(); expect(nextFive, [1, 2, 3, 4, 5]); -
[Breaking]
FutureBeacon.toFuture()now returns immediately when it's not in the loading state. This is breaking because in previous versions, it would wait for the next update before returning the value. This was a bug! To get the next state you can use.next(). -
[Feat]
FutureBeacon.toFuture()now has aresetIfErroroption that will reset the beacon if the current state isAsyncError.
-
-
0.40.004 Mar 2024Release notes
Open source →-
[Feat] Add
BeaconControllerfor use in Flutter. see docs -
[Feat] Implement
Disposablefrom basic_interfaces package which makes it autodispsable when used with the lite_ref package. -
[Docs] Add section on
testingto the README. -
[Feat] Add
synchronousoption to wrap and chaining methods. This defaults totruewhich means that wrapper beacons will get all updates. -
[Breaking] Duration is now a positional argument for chaining methods
yourBeacon.debounce(),yourBeacon.throttle(),yourBeacon.bufferTime(). This was done to make the code more concise.- Old:
final myBeacon = Beacon.writable(0); myBeacon.debounce(duration: k10ms); myBeacon.throttle(duration: k10ms); myBeacon.bufferTime(duration: k10ms);- New:
final myBeacon = Beacon.writable(0); myBeacon.debounce(k10ms); myBeacon.throttle(k10ms); myBeacon.bufferTime(k10ms); -
[Deprecation]
yourBeacon.streamis nowyourBeacon.toStream(). This was done to allow auto-batching configuration. By default, auto-batching is enabled. You can disable it by settingsynchronoustotrue.- Old:
final myBeacon = Beacon.writable(0); myBeacon.stream;- New:
final myBeacon = Beacon.writable(0); myBeacon.toStream();
-
-
0.39.128 Feb 2024Release notes
Open source →- Minor refactor to improve performance.
- Add
BeaconObserver.useLogging()as an alias toBeaconObserver.instance = LoggingObserver(). - Reduce sdk constraint to ^3.0.0 from ^3.1.5
-
0.39.023 Feb 2024Release notes
Open source →-
[Breaking] Beacons will no longer be reset when disposed. It will keep its current value.
-
[Breaking] Writing to a disposed beacon will throw an error. Reading will print a warning to the console in debug mode. A beacon should only be disposed if you have no more use for it. If you want to reuse a beacon, use the
resetmethod instead.final a = Beacon.writable(10); a.dispose(); a.value = 20; // throws an error print(a.value); // prints 10 -
[Breaking] When a beacon is disposed, all downstream derived beacons and effects will be disposed as well.
final a = Beacon.writable(10); final b = Beacon.writable(10); final c = Beacon.derived(() => a.value * b.value); a.subscribe((_) {}); Beacon.effect( () { c.value; }, name: 'effect', ); //...// a.dispose(); // "c" is watching "a" so it is disposed // the effect is watching "c" so it is disposed // // a b // | / // | / // c // | // effect expect(a.isDisposed, true); expect(c.isDisposed, true); // effect is also disposed
-
-
0.38.020 Feb 2024Release notes
Open source →- [Feat] Add methods to
Beacon.streamRawthat operates on the internal stream:unsubscribepauseandresume. - [Feat]
onDisposenow returns a function that can be used to unregister the dispose listener. - [Breaking]
anybeacon.next()no longer takes a timeout parameter. It will also throw an error if called on a lazy beacon and the beacon is disposed before emitting a value; unless a [fallback] value is provided.
Removed Deprecated methods:
anybeacon.toStream()is now removed. Useanybeacon.streaminstead. - [Feat] Add methods to
-
0.37.020 Feb 2024Release notes
Open source →- [Breaking] Remove
unsubscribemethod fromBeacon.streamRaw - [Fix] Bug when using Flutter scheduler where effects were not running before
runAppwas called. - [Refactor] Internal refactor
- [Breaking] Remove
-
0.36.018 Feb 2024Release notes
Open source →- [Breaking]
Beacon.streamandBeacon.streamRawwill now autosleep when they have no more listeners. This is a breaking change because it changes the default behavior. If you want to keep the old behavior, setshouldSleepto false.
They will unsubscribe from the stream when sleeping and resubscribe when awoken. For
Beacon.stream, it will enter the loading state when awoken. It is recommended to use the default when using services like Firebase to prevent cost overruns. - [Breaking]
-
0.35.017 Feb 2024Release notes
Open source →- [Breaking] The filter function is now required when chaining the filtered beacon.
old:
final count = Beacon.writable(10); final filtered = count.filter(filter: (prev, next) => next.isEven);new:
final count = Beacon.writable(10); final filtered = count.filter((prev, next) => next.isEven); -
0.34.417 Feb 2024 -
0.34.317 Feb 2024Release notes
Open source →- [Feat] Add
mapto chaining methods
final count = Beacon.writable(10); final mapped = count.map((value) => value * 2); expect(mapped.value, 20); count.value = 20; expect(count.value, 20); expect(mapped.value, 40);final stream = Stream.periodic(k1ms, (i) => i).take(5); final beacon = stream .toRawBeacon(isLazy: true) .filter((_, n) => n.isEven) .map((v) => v + 1) .throttle(duration: k1ms); await expectLater(beacon.stream, emitsInOrder([1, 3, 5]));See docs for more information.
- [Feat] Add
-
0.34.217 Feb 2024Release notes
Open source →-
[Feat] Expose the list of beacons as a
Readable<List<BeaconType>>in the family beacon's cache.final myFamily = Beacon.family((int id) => Beacon.writable(0)); final beacons1 = family(1); Beacon.effect((){ print('cache updated: ${myFamily.beacons.value}'); }); final beacons2 = family(2); // prints: cache updated: [beacons1, beacons2]
-
-
0.34.116 Feb 2024 -
0.34.015 Feb 2024Release notes
Open source →-
[Breaking]
Beacon.streamandBeacon.streamRawnow takes a function that returns a stream instead of a stream directly. The upside of this change is that they are now derived beacons. All beacons accessed in the function will be tracked as dependencies. This means that if one of their dependencies changes, it will unsubscribe from the old stream and subscribe to the new one. This is a breaking change because it changes the signature of the method. -
[Feat] Use can now manually start a stream beacon. It will start in the idle state when
manualStartis true. -
[Deprecated]
Beacon.derivedStreamis now deprecated. UseBeacon.streaRawinstead. -
[Deprecated]
Beacon.derivedFutureis now deprecated. UseBeacon.futureinstead. -
[Deprecated]
Beacon.batchis now deprecated. Batching is automatic with the new core. -
[Breaking]
cancelRunningis now removed fromFutureBeacon. It is now the default behavior.
New Core:
This is a major update with many breaking changes. The core of state_beacon was rewritten from scratch to be more efficient and to support more use-cases.
Pros:
- Automatic batching
- Asynchronous by default
- Better performance for deep dependency trees/circular dependencies
- Scheduler customization A scheduler is just a function that decides when to run all queued effects(flushing). By default, flushing is done with a microtask from DARTVM. This can be customized depending on your use case. For example, the flutter package ships with a scheduler that uses flutter's SchedulerBinding to handle flushing; as well as a 60fps scheduler that limits flushing to 60 times per second. Here is how you'd use them
BeaconScheduler.useFlutterScheduler(); BeaconScheduler.use60fpsScheduler();For flutter apps, it's recommended to use the flutter scheduler. The method must be called in the main function of your app.
void main() { BeaconScheduler.useFlutterScheduler(); runApp(const MyApp()); }Cons:
- Default asynchrony introduces an inconvenience with testing. Effects are queued and the scheduler decides when to flush the queue. This is ideal for apps but makes testing harder because you have to manually flush the effect queue after updating a beacon to run all effects that depends on it. This can be done by calling
BeaconScheduler.flush()after updating the beacon.
[!NOTE]
This only applies to pure dart tests. In widgets tests, callingtester.pumpAndSettle()will flush the queue.final a = Beacon.writable(10); var called = 0; // effect is queued for execution. The scheduler decides when to run the effect Beacon.effect(() { print("current value: ${a.value}"); called++; }); // manually flush the queue to run the all effect immediately BeaconScheduler.flush(); expect(called, 1); a.value = 20; // effect will be queued again. BeaconScheduler.flush(); expect(called, 2); -
-
0.33.616 Feb 2024 -
0.33.516 Feb 2024 -
0.33.5+116 Feb 2024Nothing published for this version
-
0.33.415 Feb 2024 -
0.33.304 Feb 2024Release notes
Open source →- Allow a duration to be null in
ThrottledBeaconandDebouncedBeaconto disable the throttle/debouncing. This makes them easier to test.
- Allow a duration to be null in
-
0.33.204 Feb 2024Release notes
Open source →- [Feat] Add
Beacon.derivedStream
Specialized
DerivedBeaconthat subscribes to the stream returned from its callback and updates its value based on the emitted values. When a dependency changes, the beacon will unsubscribe from the old stream and subscribe to the new one.Example:
final userID = Beacon.writable<int>(18235); final profileBeacon = Beacon.derivedStream(() { return getProfileStreamFromUID(userID.value); }); - [Feat] Add
-
0.33.131 Jan 2024Release notes
Open source →- [Fix] All delegated writes will be forced to account for the fact that rollback isn't possible.
-
0.33.031 Jan 2024Release notes
Open source →- [Feat] Chaining beacons is now supported. When the beacon returned from a chain is mutated, the mutation is re-routed to the first beacon in the chain.
final query = Beacon.writable(''); const k500ms = Duration(milliseconds: 500); final debouncedQuery = query .filter((prev, next) => next.length > 2) .debounce(duration: k500ms);When
debouncedQueryis mutated, the mutation is re-routed toquery, thenfilterand finallydebounce.NB: Buffered beacons cannot be mid-chain. If they are used, they must be the last beacon in the chain.
// GOOD someBeacon.filter().buffer(10); // BAD someBeacon.buffer(10).filter(); -
0.32.230 Jan 2024 -
0.32.129 Jan 2024Release notes
Open source →-
[Feat] Any writable can now wrap a stream with the new .ingest() method.
final myBeacon = Beacon.writable(0); myBeacon.ingest(anyStream); -
[Feat]
RawStreamBeacons can now be initialized lazily by setting theisLazyoption to true.
-
-
0.32.029 Jan 2024Release notes
Open source →-
[Feat] Add
.streamgetter for all beacons -
[Deprecation]
toStream()is now deprecated. Use.streaminstead.// before final myBeacon = Beacon.writable(0); final myStream = myBeacon.toStream(); // after final myBeacon = Beacon.writable(0); final myStream = myBeacon.stream;
-
-
0.31.229 Jan 2024Release notes
Open source →-
[Perf] This is an internal change. Only create 1
StreamControllerper beacon. -
[Deprecation] The
broadcastoption fortoStream()is now deprecated as it's now redundant.// before final myBeacon = Beacon.writable(0); final myStream = myBeacon.toStream(broadcast: true); // after final myBeacon = Beacon.writable(0); final myStream = myBeacon.toStream();
-
-
0.31.128 Jan 2024 -
0.31.027 Jan 2024Release notes
Open source →-
[Breaking] Derived and DerivedFuture beacons will now enter a sleep state when a widget/effect watching it is unmounted/disposed.
Currently derived & derivedFuture beacons always execute even if it has no listeners. It will now enter a sleep state when nothing is watching it.
Pro: No unneeded computation so it saves battery life. Con: It will not have the latest state when a widget starts watching it again so it will be in the loading state when woken up.
It is configurable with a
shouldSleepoption which defaults to true.NB: It still start eagerly, the above only kicks in when listeners decrease from 1 to 0. If you want a lazy start, just declare it as a late variable.
// with late keyword, someFuture won't run until stats is used. late final stats = Beacon.derivedFuture(() async => someFuture());
-
-
0.30.025 Jan 2024Release notes
Open source →-
[Feat] Add BeaconGroup that allows you to group beacons together and dispose/reset them all at once.
final myGroup = BeaconGroup(); final name = myGroup.writable('Bob'); final age = myGroup.writable(20); age.value = 21; name.value = 'Alice'; myGroup.resetAll(); print(name.value); // Bob print(age.value); // 20 myGroup.disposeAll(); print(name.isDisposed); // true print(age.isDisposed); // true -
[Fix] resetting an uninitialized lazybeacon no longer throws an error.
-
-
0.29.325 Jan 2024Release notes
Open source →- [Feat] Add ability to do optimistic update when using AsyncValue.tryCatch()
-
0.29.225 Jan 2024Release notes
Open source →- Internal refactor that enables deeply nested untracked withing nested effects. This is just covers a rare edge case and should not affect any existing code.
-
0.29.124 Jan 2024