NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #3957 most downloaded on pub.dev
A reactive primitive and simple state managerment solution for dart and flutter
Last release 1 months ago
26 Aug 2026
Ships unpredictably
gaps range from 8 days to 1.4 years
Nearly every release is documented
notes for 60 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
3 years old
142 releases · first in 2023
fix orphan bug with derived subscriptions by @zupat in #202
Full Changelog: v3.1.1...v3.1.2
One column per month.
add asError getter to the AsyncValue base class and its AsyncError subsclass by @zupat in #196
Full Changelog: v3.1.0...v3.1.1
asError getter to the AsyncValue. This allows users to easily retrieve the AsyncError instance when the state is an error, or null otherwise.refactor: improve edge-case status check logic in DerivedSubscription by @zupat in #173
Full Changelog: v3.0.1...v3.1.0
[Feat] Add ProgressBeacon (Beacon.progress) with support for status tracking (ProgressStatus), manual control (start/stop/pause/resume), looping, and BeaconGroup integration.
[Refactor] Internal improvements for DerivedSubscription and DerivedBeacon status propagation.
refactor error handling in FutureBeacon to use pattern matching by @zupat in #169
Full Changelog: v3.0.0...v3.0.1
[Breaking] anyBeacon.next() now throws if the beacon is disposed while waiting for the next value. This is a breaking change because previously it wou…
[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 returns null if the beacon is disposed while waiting for the next value.
Full Changelog: v2.0.3...v3.0.0
- [chore] Update repo links
fix- dirty status calculation in derived which behaved wrong when the value is nullable by @JinyuS in #164
Full Changelog: v2.0.2...v2.0.3
-[Fix] Nullable derived beacons didn't send notifications in some instances.
Create special class for subscriptions on derived beacons by @JinyuS in #159
Full Changelog: v2.0.0...v2.0.2
select,select2 and select3 methods to BeaconController which makes it easier to watch 1-3 beacons from a Controller.final (age, name) = myController.select2(context, (c) => (c.ageBeacon, c.nameBeacon));
final usernameField = TextEditingBeacon(text:'', group:B)
final usernameField = B.textEditing(text:'')
[Refactor] Internal efficiency refactor.
Remove synchronous subscriptions on DerivedBeacon: Only available for Writable and BufferedBeacons.
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
[Breaking] anyBeacon.toStream() is now anyBeacon.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 lazyBypass was true by default. This caused confusion as most persons expect it to be filtered. The name of the parameter has been changed to allowFirst and it is set to false by default.
final count = Beacon.writable(0);
final gtThan10 = count.filter((prev,next) => next>10);
expect(gtThan10.peek(), 0); // first value was set immediately
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 allowFirst to true.
final ms500 = Duration(milliseconds:500);
final count = Beacon.writable(0);
final d = count.debounce(ms500);
expect(d.peek(), 0); // first value was set immediately
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 for Writable and Buffered beacons through the .subscribeSynchronously() method.
[Breaking] Chaining methods are now asynchronous and return immutable beacons. ie: map, filter, debounce, throttle, buffer, and bufferTime.
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.
fix: defer synchronous subscription disposal to prevent RangeError by @JinyuS in #154
Full Changelog: v1.3.3...v1.3.4
Deprecate supportConditional parameter in effect methods by @JinyuS in #146
Full Changelog: v1.3.2...v1.3.3
force flag when those writes were added to the buffer.Fix method chaining for derived sources by @JinyuS in #142
[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);
[Feat] Add queuing to FutureBeacon.updateWith() The updateWith method calls are now queued when there is an ongoing update. This ensures that all call
updateWith method 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.The updateWith method allows you to update a FutureBeacon's value with the provided callback. This differs from overrideWith because it updates the va
Future.updateWith()The updateWith method allows you to update a FutureBeacon's value with the provided callback. This differs from overrideWith because it updates the value only once, while overrideWith replaces 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] 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;
});
[Fix] Bug that caused some widgets to not rebuild when using go_router et al.
Bug Fix: Flutter edge-case for derivedBeacons. This was fixed before but the current fix is more efficient.
- Stable release
[Fix] Edge case for Subscriptions
[Dependency] Updated lite_ref to 0.8.1
lite_ref to 0.8.1[Feat] Add ValueNotifier.toBeacon() which converts a ValueNotifier to a WritableBeacon. All changes to the notifier are reflected in the beacon and vi
[Feat] Add ValueNotifier.toBeacon() which converts a ValueNotifier to a WritableBeacon. All changes to the notifier are reflected in the beacon and vice versa.
[Feat] Add TextEditingBeacon which is a beacon that wraps a TextEditingController. All changes to the controller are reflected in the beacon and vice versa.
overrides property of LiteRefScope is now a Set<ScopedRef> instead of a List<ScopedRef>.[Fix] Fix bug with FutureBeacons not autosleeping
FutureBeacons not autosleepingBeaconGroup.beaconsBeaconGroup.onCreate to allow adding a callback to be run when a beacon is created[Fix] Rare bug in FutureBeacon when start is called multiple times synchronously.
[Feat] Add FutureBeacon.idle() to set a beacon to the AsyncIdle state.
FutureBeacon.idle() to set a beacon to the AsyncIdle state.[Feat] Add the ability for widgets to observe beacons synchronously. When synchronous is true, autobatching will be disabled and all updates will be e
synchronous is true, autobatching will be disabled and all updates will be emitted immediately.final beacon = Beacon.writable(10);
beacon.observe(context, (prev, next) {}, synchronous: true);
lite_ref to 0.6.3[Dependency] Updated lite_ref to 0.6.2
lite_ref to 0.6.2[Breaking] The beacons getter for Beacon.family has been replaced with entries. This is a breaking change because it returns a MapEntry instead of a B…
[Breaking] The beacons getter for Beacon.family has been replaced with entries. This is a breaking change because it returns a MapEntry<Key,Beacon> instead of a Beacon.
[Dependency] Updated lite_ref to 0.6.1
[Fix] Update lite_ref dependency and add flutter dependency constraint
[Fix] Export PeriodicBeacon and BeaconFamily classes
[Breaking] resetIfError option for toFuture() is now true by default. This was done because there's rarely a case where you'd want it to throw instant
resetIfError option for toFuture() is now true by 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, set resetIfError to false.- [Docs] Update README
[Feat] Export lite_ref as the recommended dependency injection mechanism for state_beacon. Added convenience methods to ScopedRef for BeaconController
[Feat] Export lite_ref as the recommended dependency injection mechanism for state_beacon. Added convenience methods to ScopedRef for BeaconControllers and Beacons
class CountController extends BeaconController {
late final count = B.writable(0);
late final doubledCount = B.derived(() => count.value * 2);
}
final countControllerRef = Ref.scoped((ctx) => CountController());
class CounterText extends StatelessWidget {
const CounterText({super.key});
@override
Widget build(BuildContext context) {
// watch the count beacon and return its value
final count = countControllerRef.select(context, (c) => c.count);
return Text('$count');
}
}
[Refactor] Move BeaconController to state_beacon_core package
BeaconController to state_beacon_core package[Feat] Add Beacon.periodic that emits values periodically.
[Feat] Add Beacon.periodic that 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 a resetIfError option that will reset the beacon if the current state is AsyncError.
[Deprecation] yourBeacon.stream is now yourBeacon.toStream(). This was done to allow auto-batching configuration. By default, auto-batching is enabled…
[Feat] Add BeaconController for use in Flutter. see docs
[Feat] Implement Disposable from basic_interfaces package which makes it autodispsable when used with the lite_ref package.
[Docs] Add section on testing to the README.
[Feat] Add synchronous option to wrap and chaining methods. This defaults to true which 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.
final myBeacon = Beacon.writable(0);
myBeacon.debounce(duration: k10ms);
myBeacon.throttle(duration: k10ms);
myBeacon.bufferTime(duration: k10ms);
final myBeacon = Beacon.writable(0);
myBeacon.debounce(k10ms);
myBeacon.throttle(k10ms);
myBeacon.bufferTime(k10ms);
[Deprecation] yourBeacon.stream is now yourBeacon.toStream(). This was done to allow auto-batching configuration. By default, auto-batching is enabled. You can disable it by setting synchronous to true.
final myBeacon = Beacon.writable(0);
myBeacon.stream;
final myBeacon = Beacon.writable(0);
myBeacon.toStream();
Minor refactor to improve performance.
BeaconObserver.useLogging() as an alias to BeaconObserver.instance = LoggingObserver().[Breaking] Beacons will no longer be reset when disposed. It will keep its current value.
[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 reset method 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
Removed Deprecated methods: anybeacon.toStream() is now removed. Use anybeacon.stream instead.
Beacon.streamRaw that operates on the internal stream: unsubscribe pause and resume.onDispose now returns a function that can be used to unregister the dispose listener.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. Use anybeacon.stream instead.
[Breaking] Remove unsubscribe method from Beacon.streamRaw
unsubscribe method from Beacon.streamRawrunApp was called.[Breaking] Beacon.stream and Beacon.streamRaw will now autosleep when they have no more listeners. This is a breaking change because it changes the de…
Beacon.stream and Beacon.streamRaw will 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, set shouldSleep to 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] The filter function is now required when chaining the filtered beacon.
final count = Beacon.writable(10);
final filtered = count.filter(filter: (prev, next) => next.isEven);
final count = Beacon.writable(10);
final filtered = count.filter((prev, next) => next.isEven);
- Internal refactor
[Feat] Add map to chaining methods
map to chaining methodsfinal 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] Expose the list of beacons as a Readable > in the family beacon's cache.
[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]
[Refactor] Internal refactor and minor improvement in performance
…stream and subscribe to the new one. This is a breaking change because it changes the signature of the method.
[Breaking] Beacon.stream and Beacon.streamRaw now 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 manualStart is true.
[Deprecated] Beacon.derivedStream is now deprecated. Use Beacon.streaRaw instead.
[Deprecated] Beacon.derivedFuture is now deprecated. Use Beacon.future instead.
[Deprecated] Beacon.batch is now deprecated. Batching is automatic with the new core.
[Breaking] cancelRunning is now removed from FutureBeacon. It is now the default behavior.
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:
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:
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);
[Fix] Add bug fix from 0.34.0 to flutter package
[Fix] Bug with auto sleeping derivedFuture beacons
[Fix] Concurrent modification error when notifying listeners.
Allow a duration to be null in ThrottledBeacon and DebouncedBeacon to disable the throttle/debouncing. This makes them easier to test.
ThrottledBeacon and DebouncedBeacon to disable the throttle/debouncing. This makes them easier to test.[Feat] Add Beacon.derivedStream
Beacon.derivedStreamSpecialized DerivedBeacon that 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);
});
[Fix] All delegated writes will be forced to account for the fact that rollback isn't possible.
[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 debouncedQuery is mutated, the mutation is re-routed to query, then filter and finally debounce.
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();
Allow initialValue to be passed to ingest method
initialValue to be passed to ingest method[Feat] Any writable can now wrap a stream with the new .ingest() method.
[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 the isLazy option to true.
[Deprecation] toStream() is now deprecated. Use .stream instead.
[Feat] Add .stream getter for all beacons
[Deprecation] toStream() is now deprecated. Use .stream instead.
// before
final myBeacon = Beacon.writable(0);
final myStream = myBeacon.toStream();
// after
final myBeacon = Beacon.writable(0);
final myStream = myBeacon.stream;
[Fix] mirror force option of wrapped beacons.
[Breaking] Derived and DerivedFuture beacons will now enter a sleep state when a widget/effect watching it is unmounted/disposed.
[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 shouldSleep option 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());
Your coding agent can read these notes before it upgrades. Set up the MCP server →