all_observer 1.5.8 copy "all_observer: ^1.5.8" to clipboard
all_observer: ^1.5.8 copied to clipboard

Lightweight reactive state management for Flutter with zero external dependencies: observable values plus an auto-tracking Observer widget.

1.5.8 #

  • Synchronized both READMEs and updated installation examples to ^1.5.8.
  • Documentation-only release with no code, API, behavior, or dependency changes.

1.5.7 #

  • Cached stable CoreObservable and CoreComputed debug labels, removing repeated formatting from reactive hot paths.
  • addListener() after close() is now an inert no-op, preventing callback retention.
  • Added regression coverage and repeatable JIT/AOT benchmarks.
  • No breaking changes and no new dependencies.

1.5.6 #

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.
  • Added remaining-risk audit tests plus permanent regression coverage for the confirmed issues. Full suite now covers 405 tests.
  • CI now runs flutter analyze, flutter test, and flutter pub publish --dry-run, with manual dispatch support.
  • No breaking changes and no new dependencies.

1.5.5 #

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.
  • Failed 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.
  • Added audit tests plus permanent regression coverage for the confirmed issues. Full suite now covers 389 tests.
  • No breaking changes and no new dependencies.

1.5.4 #

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.
  • Added regression coverage for graph mutations during dirty checking, subscriber disposal while sibling subscribers update, dependencies losing their last subscriber, 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.
  • No breaking changes and no new dependencies.

1.5.3 #

Patch release focused on async safety and regression hardening.

  • ObservableStream.run() now converts exceptions thrown synchronously by streamFactory into AsyncError, matching ObservableFuture.
  • Failures returned asynchronously by StreamSubscription.cancel() are isolated and reported through CoreErrorReporting.
  • Added deterministic graph fuzzing, 20,000-cycle memory-retention stress, collection model tests, lifecycle/churn coverage, public-entrypoint smoke tests, and broad relative performance guards.
  • Added a web release build of the example app to CI, including Flutter's Wasm compatibility dry run.
  • Behavior change: synchronous exceptions from 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.

1.5.2 #

  • Added 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.
  • Documented custom logging with ObserverInspector, including multiple inspectors, external log sinks, optional stack traces, exception isolation, and RecordingInspector audit trails.
  • Added regression coverage for Observer lifecycle and subscription cleanup, closed observables, collection notifications, ReactiveScope disposal, CoreComputed dependency cleanup, high-frequency updates, and custom inspectors.
  • No breaking changes.

1.5.1 #

  • 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.
  • No breaking changes. Documented in documentation/*/collections.md; covered by new tests in test/observable/collections/observable_list_test.dart.

1.5.0 — Engine v2 #

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.

  • New public entry point 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.notifyAllpropagate), 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.
  • Self-dependency detection: a 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.
  • Engine test suites: 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).

1.4.0 #

Additive release — no breaking changes, no new external dependencies.

  • Surgical rebuilds with watch(context): observable.watch(context) (and computed.watch(context)) subscribes the calling widget's own ElementObserver 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.
  • Scoped cleanup with 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 ObserverStateMixinscoped(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.
  • Inspector: new 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).
  • Docs: new "Surgical rebuilds with 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.

1.3.2 #

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).

1.3.1 #

Documentation-only release — no code changes, no breaking changes, no new external dependencies.

  • Restructured 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).
  • Added a hero image to both READMEs.

1.3.0 #

Additive release — no breaking changes, no new external dependencies.

  • Core/Flutter split: the reactive engine (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.
  • Standalone reactivity: effect(), plus escape hatches untracked(), Observable.peek(), and Observable.previousValue.
  • Pluggable observability: the 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.
  • Async: ObservableStream<T>, the Stream counterpart of ObservableFuture, with the same generation-counter race safety. AsyncValue<T> added as an alias for AsyncState<T>.
  • Lifecycle helpers: 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).
  • Docs: 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.

1.2.0 #

Minor release — no breaking changes, no new external dependencies.

Core:

  • T2.1 — Auto-batch (glitch-free by default): every write — even a standalone 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.
  • "Known limitations" section updated in both READMEs: removed "Diamond glitch outside batch" and "Deeper cross-branch cascades" items; replaced with a paragraph explaining that 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.

1.1.1 #

Patch release — no breaking changes, no new external dependencies.

Bug fixes (audit):

  • T1.1 — Flush wave limit (CRITICAL): _flushPending in batch_scope.dart now has a bounded iteration limit (kMaxFlushWaves = 100). A mutual cycle a.listen((v)=>b.value=v+1) + b.listen((v)=>a.value=v+1) inside a batch() previously caused an infinite loop because kMaxNotificationDepth only guards nested call-stack recursion, not iterative while waves. The new guard detects the wave overflow, clears pending queues, and reports a descriptive bilingual FlutterError instead of hanging indefinitely.
  • T1.2 — previousData survives chained run() calls: calling ObservableFuture.run() a second time before the first completes previously silently erased the stale value (.valueOrNull returned null when state was already AsyncLoading). Fixed via _previousDataFromCurrent() helper: AsyncData → preserve its value; AsyncLoading → propagate its own previousData; AsyncErrornull (documented deliberate choice).

Documentation:

  • T1.3 — Observable.refresh() polymorphic semantics: new bilingual paragraph documenting that subclasses may extend refresh() beyond a simple notification (e.g. ObservableFuture.refresh re-runs the fetch). No code change.

Tests: +7 new tests (4 for T1.1 wave-limit scenarios, 3 for T1.2 chained run() cases); total 163.

1.1.0 #

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.
  • Diamond-glitch mitigation: inside an active 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/.
  • README (EN/PT-BR): new "Migrating from other reactive state solutions" section (concept-based equivalence table) and a "Known limitations" section (diamond glitch outside batch, Computed staying active after first read until close(), single-isolate confinement).

1.0.0 #

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.
  • Workers: ever, once, debounce, interval, plus Worker/Workers.

Robustness:

  • An exception thrown inside one 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.
  • A synchronous update cycle (a listener of A writing to B, whose listener writes back to A) stops after a bounded notification depth with a descriptive 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.
  • The "write during build" warning covers both 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.
  • Documented, in Observable's dartdoc, that writing to an observable from a different isolate does not work (no cross-isolate synchronization is attempted).

Other:

  • Debug-only, ANSI-colored logging via ObserverConfig and non-fatal misuse warnings (with optional strictMode).
  • Zero external dependencies; full ValueListenable interoperability.

See AUDIT_REPORT.md at the repository root for the full item-by-item performance/robustness audit this release was validated against.

3
likes
160
points
1.12k
downloads

Documentation

API reference

Publisher

verified publisheropensource.tatamemaster.com.br

Weekly Downloads

Lightweight reactive state management for Flutter with zero external dependencies: observable values plus an auto-tracking Observer widget.

Repository (GitHub)
View/report issues
Contributing

Topics

#state-management #reactive #observable #reactive-programming

License

MIT (license)

Dependencies

flutter

More

Packages that depend on all_observer