NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev
Lightweight reactive state management for Flutter with zero external dependencies: observable values plus an auto-tracking Observer widget.
Last release 2 months ago
26 Jul 2026
Ships on a steady schedule
a new release about every 2 weeks
Nearly every release is documented
notes for 16 of 16 stable releases
Nothing withdrawn
no release was ever pulled
3 months old
16 releases · first in 2026
One column per month.
Synchronized both READMEs and updated installation examples to ^1.5.8.
^1.5.8.No breaking changes and no new dependencies.
CoreObservable and CoreComputed debug labels, removing
repeated formatting from reactive hot paths.addListener() after close() is now an inert no-op, preventing callback
retention.No breaking changes and no new dependencies.
Patch release focused on the remaining 1.5.5 audit gaps for same-flush
effects, ObservableSet atomicity, toSet() tracking, and CI hardening.
effect() now consumes same-registry self-invalidation suppression once, so
a later external write to the same observable in the same flush is still
observed.ObservableSet.addAll() is atomic when the source iterable throws, matching
the list behavior and avoiding silent partial mutations.ObservableSet.toSet() now registers a reactive read, so Computed and
effect() bodies based only on toSet() update correctly.flutter analyze, flutter test, and
flutter pub publish --dry-run, with manual dispatch support.No breaking changes and no new dependencies.
Patch release focused on audit-confirmed correctness fixes for reactive failure recovery, closed async resources, and collection atomicity.
Computed now retries correctly after an initial compute error instead of
leaving the derivation poisoned by the failed evaluation.effect() now cleans up tracked dependencies when creation throws, avoiding
inaccessible zombie effects that could run again on later dependency writes.effect() scheduling now distinguishes direct self-invalidations from
indirect same-flush invalidations, so chained effect/computed graphs converge
to the latest value.Observable.batch() calls still discard queued direct notifications,
but already-live computed dependents are marked stale so the next read
reconciles with the mutated source value.ObservableFuture.run() and ObservableStream.run() are no-ops after
close(), preventing closed async resources from starting new work or stream
subscriptions.ObservableList.addAll(), insertAll(), assignAll(), and sort() are
atomic when the source iterable or comparator throws; the list remains
unchanged and no notification is emitted.No breaking changes and no new dependencies.
Patch release focused on effect scheduler safety and regression coverage for push-pull graph edge cases.
effect() now suppresses self-invalidations caused by writes made inside
the effect body during the same batch flush. This prevents a duplicate
compensating run while preserving later external updates.effect() disposal is safer during re-entrant execution: disposing an
effect, or its owning ReactiveScope, from inside the callback now removes
freshly tracked dependencies instead of restoring stale listeners.CoreComputed.close() during recomputation,
untracked() inside CoreComputed, same-batch value reverts, batch-flush
exception isolation, nested effect ownership, and Dart2JS compilation of
core.dart/engine.dart.Patch release focused on async safety and regression hardening.
Patch release focused on async safety and regression hardening.
ObservableStream.run() now converts exceptions thrown synchronously by
streamFactory into AsyncError, matching ObservableFuture.StreamSubscription.cancel() are
isolated and reported through CoreErrorReporting.streamFactory no longer
escape from ObservableStream.run(). Consumers that previously caught that
exception with try/catch must now handle it through the emitted
AsyncError. Public API signatures and reactive-engine behavior are
unchanged.Added ObsBool, ObsInt, ObsDouble, and ObsString as lightweight typedef aliases for common Observable types. They preserve the complete Observable cons
ObsBool, ObsInt, ObsDouble, and ObsString as lightweight
typedef aliases for common Observable<T> types. They preserve the
complete Observable constructor and behavior without adding classes.ObserverInspector, including multiple
inspectors, external log sinks, optional stack traces, exception isolation,
and RecordingInspector audit trails.ReactiveScope disposal,
CoreComputed dependency cleanup, high-frequency updates, and custom
inspectors.No breaking changes. Documented in documentation/*/collections.md; covered by new tests in test/observable/collections/observable_list_test.dart.
ObservableList<E> gained factory constructors mirroring List's
static equivalents: .filled, .empty, .from, .of, .generate,
.unmodifiable. All accept an optional name for the debug label.
Internally they route through a new private _owned constructor that
takes ownership of an already-fresh list instead of copying it again,
so these factories don't pay for an extra allocation on top of the one
List.filled/.generate/etc. already did.ObservableList<E> gained convenience mutators: assign/assignAll
(replace every element, notifying exactly once instead of once for an
implicit clear() plus once for the add/addAll), addIf/addAllIf
(add only when a bool condition is true), and addIfNotNull (add
only when the value isn't null). All respect the same closed-collection
no-op semantics as every other mutating member.documentation/*/collections.md;
covered by new tests in test/observable/collections/observable_list_test.dart.It's the biggest change under the hood since the package's first release — and it costs you nothing: no breaking changes, no new dependencies, same pu…
all_observer now runs on its own push-pull reactive graph engine, built
in-house from the ground up. It's the biggest change under the hood since
the package's first release — and it costs you nothing: no breaking
changes, no new dependencies, same public API. Every documented behavior
contract (glitch-free two-phase batching, in-batch read staleness, eager
per-flush settling, equals filtering, per-listener error isolation,
inspectors) is preserved, and the pre-existing 286-test suite passes
unmodified.
Why it matters: the new engine is O(1) on link/unlink, reuses links across
re-tracks with zero allocation when your dependency set doesn't change, and
propagates fully iteratively — so deeply nested reactive graphs are bounded
by available heap instead of the call stack. It also now ships as its own
public entry point, so you can build a custom reactive layer directly on
top of the same core engine that powers all_observer itself.
package:all_observer/engine.dart: a
standalone, pure-Dart, policy-free reactive graph engine
(ReactiveEngine, ReactiveNode, ReactiveLink, ReactiveFlags)
designed for third parties to build their own reactive layers on — the
same machinery all_observer itself now runs on. Dependencies are
intrusive doubly-linked lists (O(1) link/unlink, links reused in place
across re-tracks — zero allocation when the read set doesn't change),
node state is bit flags in a single int (via a zero-cost
extension type), and both propagation phases are fully iterative
(explicit stack), so graph depth is bounded by heap instead of the call
stack. Behavior is delegated to three hooks: update (recompute; its
bool return is the propagation cut), notify (schedule a watcher) and
unwatched (last-subscriber cleanup). Tutorial in
documentation/*/engine.md; a minimal, runnable signals implementation
built on it lives in test/engine/fixtures/mini_preset.dart.CoreComputed rewired onto the engine (lib/src/core/engine_bridge .dart, exported by core.dart): dependency tracking moved from
per-recompute registry subscriptions (clear-all + resubscribe, one
closure disposer per dependency per run) to engine links, and
invalidation became push-pull — a flushed notification only marks
dependents stale (ListenerRegistry.notifyAll → propagate), and an
internal WatcherNode (created on first evaluation, kept until
close()) pulls the fresh value in phase 2 of the same BatchScope
flush as always. Reads landing between marking and settling resolve
lazily on the spot (checkDirty), which also settles deep computed
cascades within a single flush wave instead of one wave per graph level.
Integration rides entirely on DependencyTracker.reportRead and
ListenerRegistry.notifyAll/notifyOrQueue, so CoreObservable,
BatchScope, collections, async, workers and widgets are untouched.
See ADR-0003 in ARCHITECTURE.md.Observable.hasListeners now also counts engine-graph dependents
(a Computed depending on the observable), preserving its long-standing
meaning now that computeds are no longer plain registry listeners.Computed whose compute() reads its
own value (directly or through a chain) now throws a descriptive
ObserverCycleError instead of overflowing the call stack.TrackingContext gained a subscribes flag (default true,
source-compatible): a recomputing CoreComputed pushes a
non-subscribing context, keeping outer-context isolation, read counting
and onTrack inspector events while the engine handles invalidation.test/engine/ (graph semantics: lazy pull,
propagation cut via custom equals, link reuse identity, conditional
branch switching, 50k-deep chains without stack overflow, batching,
automatic unwatched cleanup) and test/core/engine_bridge_test.dart
(bridge semantics against the package contracts).Additive release — no breaking changes, no new external dependencies.
Additive release — no breaking changes, no new external dependencies.
watch(context): observable.watch(context)
(and computed.watch(context)) subscribes the calling widget's own
Element — Observer semantics with no wrapper widget. Dependencies are
re-discovered per build, multiple watched observables coalesce into one
rebuild per batch/frame, rebuild scheduling shares the exact
scheduler-phase handling Observer uses (extracted into an internal
helper), and onTrack inspector events fire with a
Watch(<widget runtimeType>) label. Cleanup is lazy by design (Element
has no third-party unmount hook): the first notification after unmount is
a guaranteed no-op that releases every subscription of that element —
documented in documentation/*/advanced.md. In debug, calling watch
outside build() warns (throws ObserverError under strictMode);
inside an Observer/Computed/effect it delegates the read to the
active tracking context instead of double-subscribing.ReactiveScope: an ambient, stack-based disposal
scope in the pure-Dart core (exported by both core.dart and
all_observer.dart). Every Computed/CoreComputed, effect() and
worker (ever/once/debounce/interval) created inside scope.run(...)
registers its disposer in the scope; scope.dispose() tears everything
down in LIFO order, idempotently, isolating disposer exceptions. Opt-in:
creation outside a scope is unchanged. Nested scopes are disposed with
their parent; add() on a disposed scope disposes the resource
immediately with a debug warning (throws under strictMode). Plain
Observables are deliberately not captured (no resource to release;
documented).ScopedObserverMixin: pure-Dart controller counterpart of
ObserverStateMixin — scoped(fn), autoDispose(disposer),
disposeScope(), isScopeDisposed, and the underlying scope exposed.ObserverStateMixin internal refactor: now runs on an internal
ReactiveScope; public API unchanged, existing suite passes unmodified.
Behavioral footnote: disposers now run in reverse registration (LIFO)
order — previously registration order — and registering after dispose
additionally dispatches a misuse warning (throws under strictMode),
matching ReactiveScope.add.ObserverInspector.onScopeDispose(ScopeDisposeEvent)
event (label + disposedCount), with a no-op default implementation like
every other event; RecordingInspector records it, ConsoleInspector
stays silent by default (as with onEffectRun).watch(context)" and "Scoped
cleanup with ReactiveScope" sections in advanced.md (EN/PT-BR),
updated comparison table (standalone effects, untracked reads,
wrapper-less rebuilds, scoped cleanup), migration_from_getx.md gained a
"Replacing onClose with ScopedObserverMixin" section, and both
READMEs show a one-line watch(context) example.Documentation/example-only release — no code changes to lib/, no breaking changes, no new external dependencies.
Documentation/example-only release — no code changes to lib/, no breaking changes, no new external dependencies.
Added real, runnable tests under example/test/ proving the library's testability: a widget test, a pure-Dart controller unit test, a measurable rebuild-granularity test, a debounce worker test using flutter_test's virtual time, a deterministic ObservableFuture test via injected fakes, and a strictMode misuse test.
Extracted the example demos' business logic into constructor-injectable controllers under example/lib/controllers/ (CounterController, FruitSearchController, FetchController) — the "testable architecture" pattern the new guide recommends.
New documentation/en/testing.md / documentation/pt-BR/testing.md guide, linking every snippet to the real test file it comes from; linked from both READMEs, both FAQs, and the doc navigation chain.
Added example/README.md and wired example/ into CI (flutter test now also runs there).
Documentation-only release — no code changes, no breaking changes, no new external dependencies.
Documentation-only release — no code changes, no breaking changes, no new external dependencies.
README.md/README.pt-BR.md into a lean landing page, with
the rest moved into topic guides under documentation/en/ and
documentation/pt-BR/ (core concepts, collections, async, workers,
advanced, comparison, migration from GetX, FAQ).Additive release — no breaking changes, no new external dependencies.
Additive release — no breaking changes, no new external dependencies.
CoreObservable,
CoreComputed, DependencyTracker, ListenerRegistry, BatchScope) now
has zero package:flutter import and is exposed standalone via
package:all_observer/core.dart. Observable/Computed are unchanged
from the outside — they now wrap this engine internally.effect(), plus escape hatches untracked(),
Observable.peek(), and Observable.previousValue.ObserverInspector interface
(onCreate/onUpdate/onDispose/onTrack/onWarning/onEffectRun),
with the classic colored console output now a formal ConsoleInspector
implementation, plus a RecordingInspector and
ObserverConfig.inspectors/captureStackTraces.ObservableStream<T>, the Stream counterpart of
ObservableFuture, with the same generation-counter race safety.
AsyncValue<T> added as an alias for AsyncState<T>.ObserverStateMixin (auto-disposed effect()s and
subscriptions on a State), ObservableStore/Observable.persistWith
(optional persistence integration point, e.g. for all_box), and
ObservableHistory/Observable.withHistory (bounded undo/redo).ARCHITECTURE.md expanded to cover all of the above,
CONTRIBUTING.md added, CI workflow added, and both READMEs gained a
comparison section against GetX/Riverpod/MobX/flutter_hooks.Tests: +57 new tests; total 225.
Minor release — no breaking changes, no new external dependencies.
Minor release — no breaking changes, no new external dependencies.
Core:
observable.value = x outside any explicit Observable.batch()
— is now automatically routed through the same two-phase fixed-point flush
that explicit batches use. Diamond dependency graphs (Computed A and B
both derived from source S, Computed C depending on A and B) always
recompute exactly once, always seeing fully settled upstream values — no
glitch, no explicit batch() required. The implementation wraps each
standalone write in a micro-batch (BatchScope.run(() => BatchScope.queue(this)));
a fast-path skips the overhead entirely when there are no listeners. The
kMaxFlushWaves = 100 guard from v1.1.1 (T1.1) is the safety net for any
in-batch cycle that could result from cascading writes.Observable.batch() repositioned as a performance optimization:
wrapping multiple writes in batch() coalesces them into a single
notification round (all writes committed first, then each changed observable
notifies once), instead of opening a micro-batch per write. Use batch()
whenever you write to more than one observable in the same logical action.batch() is now a performance tool, not
a consistency requirement.Tests: +5 new tests (T2.1 auto-batch group: diamond without batch, 3-level cascade, write-in-ever-callback, explicit-batch coalescing, post-close no-op); total 168.
Additive release — no breaking changes, no new external dependencies.
Additive release — no breaking changes, no new external dependencies.
Async:
ObservableFuture<T>: an Observable<AsyncState<T>> that runs a Future<T> Function() and tracks its loading/data/error lifecycle. Auto-runs by default (autoStart: true) or via run()/refresh(). Race-safe: a generation counter discards a stale run()'s result if a newer run() started before it resolved, and discards any result that arrives after close().AsyncState<T> sealed class: AsyncLoading<T> (with previousData for stale-while-loading UIs), AsyncData<T>, AsyncError<T>; when/maybeWhen, isLoading/hasData/hasError/valueOrNull, content-based ==/hashCode.Computed:
equals named parameter on Computed's constructor, mirroring Observable's, for custom change-detection (e.g. floating-point tolerance) on the recomputed value.Observable.batch(), a Computed's recompute is deferred (to the next value read, or to end-of-batch, whichever comes first) instead of running eagerly per upstream notification — so a diamond-shaped dependency graph recomputes at most once per batch, with every dependency already at its final value. Outside batch, the previous eager-recompute-per-notification behavior is unchanged and documented as a known limitation.Widgets:
Observer.withChild named constructor: rebuilds only the part of the subtree the builder itself constructs, reusing a static child widget across rebuilds instead of reconstructing it — for expensive widgets that don't depend on any observable.Observable:
Observable.setValue(T newValue): equivalent to value = newValue, usable as a tear-off (e.g. directly as an onChanged callback) and the unambiguous way to assign null to an Observable<T?> (unlike call(null), which reads instead of assigns).Observable<T>.select<R>(R Function(T value) selector, {String? name}) extension: sugar over Computed(() => selector(value), name: name) for a narrower, memoized derived value. Caller owns and must close() the returned Computed.Performance:
ListenerRegistry now backed by a LinkedHashSet<VoidCallback> instead of a List, making add/remove/contains O(1) instead of O(n), while preserving insertion order and native deduplication. See benchmark/listener_registry_benchmark.dart.Other:
example/ reworked into a multi-page app (bottom navigation) with five focused demos: counter + Computed, debounced search, ObservableFuture async states, Observable.batch form saving, and Observer/ValueListenableBuilder interop — each in its own file under example/lib/demos/.batch, Computed staying active after first read until close(), single-isolate confinement).Observable reactive value, plus ObservableInt, ObservableDouble, ObservableBool, ObservableString.
Initial release.
Core:
Observable<T> reactive value, plus ObservableInt, ObservableDouble, ObservableBool, ObservableString..obs extension for creating observables from literals and collections.ObservableList, ObservableMap, ObservableSet reactive collections; reading any member tracks it, mutating any member notifies at most once per call — bulk operations (addAll, removeWhere, retainWhere, insertAll, clear, sort, shuffle) never notify once per element, and no-op mutations (adding a duplicate Set element, removeWhere that removes nothing, map[k]=v with an identical value, clear() on an already-empty collection) don't notify at all.Observer widget with automatic, stack-based dependency tracking and safe rebuild scheduling (no "defunct element" crashes).ObserverValue for self-contained local reactive state.Computed<T>: lazy, memoized derived values reusing the same dependency tracker as Observer; supports dynamic/conditional dependencies; only notifies when the derived value actually changes; close() unsubscribes from all current dependencies.Observable.batch(() { ... }): coalesces multiple writes inside the callback into a single notification per changed observable/collection for manual (listen/ever) subscribers, with nested-batch support.equals parameter on the Observable<T> constructor, for custom change-detection (e.g. floating-point tolerance) instead of ==.listen/ObservableSubscription, stream-free manual subscriptions.ever, once, debounce, interval, plus Worker/Workers.Robustness:
ever/listen callback does not stop other listeners of the same observable from running; it is reported via FlutterError.reportError (library: 'all_observer') instead of being swallowed or propagating.FlutterError, instead of crashing with a raw stack overflow.Observer: if the builder throws during a tracked build, dependency disposers accumulated up to that point are still assigned, so the next build/unmount does not leak or double-register listeners.Observable.value = and reactive-collection mutations, and ObserverConfig.strictMode turns this case into a thrown ObserverError as well as the "empty Observer" case.listen()/close() on an already-close()d Observable/collection returns an inert (already-canceled) subscription instead of silently registering a listener that can never fire; a mutation attempted on a closed collection is a full no-op (the underlying data is left untouched), and double-close() is safe.Observable's dartdoc, that writing to an observable from a different isolate does not work (no cross-isolate synchronization is attempted).Other:
ObserverConfig and non-fatal misuse warnings (with optional strictMode).ValueListenable interoperability.See AUDIT_REPORT.md at the repository root for the full item-by-item performance/robustness audit this release was validated against.
Your coding agent can read these notes before it upgrades. Set up the MCP server →