all_observer 1.5.8
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
CoreObservableandCoreComputeddebug labels, removing repeated formatting from reactive hot paths. addListener()afterclose()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, soComputedandeffect()bodies based only ontoSet()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, andflutter 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.
Computednow 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()andObservableStream.run()are no-ops afterclose(), preventing closed async resources from starting new work or stream subscriptions.ObservableList.addAll(),insertAll(),assignAll(), andsort()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 owningReactiveScope, 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()insideCoreComputed, same-batch value reverts, batch-flush exception isolation, nested effect ownership, and Dart2JS compilation ofcore.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 bystreamFactoryintoAsyncError, matchingObservableFuture.- Failures returned asynchronously by
StreamSubscription.cancel()are isolated and reported throughCoreErrorReporting. - 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
streamFactoryno longer escape fromObservableStream.run(). Consumers that previously caught that exception withtry/catchmust now handle it through the emittedAsyncError. Public API signatures and reactive-engine behavior are unchanged.
1.5.2 #
- Added
ObsBool,ObsInt,ObsDouble, andObsStringas lightweighttypedefaliases for commonObservable<T>types. They preserve the completeObservableconstructor and behavior without adding classes. - Documented custom logging with
ObserverInspector, including multiple inspectors, external log sinks, optional stack traces, exception isolation, andRecordingInspectoraudit trails. - Added regression coverage for Observer lifecycle and subscription cleanup,
closed observables, collection notifications,
ReactiveScopedisposal,CoreComputeddependency cleanup, high-frequency updates, and custom inspectors. - No breaking changes.
1.5.1 #
ObservableList<E>gained factory constructors mirroringList's static equivalents:.filled,.empty,.from,.of,.generate,.unmodifiable. All accept an optionalnamefor the debug label. Internally they route through a new private_ownedconstructor 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 oneList.filled/.generate/etc. already did.ObservableList<E>gained convenience mutators:assign/assignAll(replace every element, notifying exactly once instead of once for an implicitclear()plus once for theadd/addAll),addIf/addAllIf(add only when aboolcondition istrue), andaddIfNotNull(add only when the value isn'tnull). 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 intest/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 machineryall_observeritself 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 singleint(via a zero-costextension 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; itsboolreturn is the propagation cut),notify(schedule a watcher) andunwatched(last-subscriber cleanup). Tutorial indocumentation/*/engine.md; a minimal, runnable signals implementation built on it lives intest/engine/fixtures/mini_preset.dart. CoreComputedrewired onto the engine (lib/src/core/engine_bridge .dart, exported bycore.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 internalWatcherNode(created on first evaluation, kept untilclose()) pulls the fresh value in phase 2 of the sameBatchScopeflush 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 onDependencyTracker.reportReadandListenerRegistry.notifyAll/notifyOrQueue, soCoreObservable,BatchScope, collections, async, workers and widgets are untouched. See ADR-0003 inARCHITECTURE.md.Observable.hasListenersnow also counts engine-graph dependents (aComputeddepending on the observable), preserving its long-standing meaning now that computeds are no longer plain registry listeners.- Self-dependency detection: a
Computedwhosecompute()reads its ownvalue(directly or through a chain) now throws a descriptiveObserverCycleErrorinstead of overflowing the call stack. TrackingContextgained asubscribesflag (defaulttrue, source-compatible): a recomputingCoreComputedpushes a non-subscribing context, keeping outer-context isolation, read counting andonTrackinspector events while the engine handles invalidation.- Engine test suites:
test/engine/(graph semantics: lazy pull, propagation cut via customequals, link reuse identity, conditional branch switching, 50k-deep chains without stack overflow, batching, automaticunwatchedcleanup) andtest/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)(andcomputed.watch(context)) subscribes the calling widget's ownElement—Observersemantics 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 handlingObserveruses (extracted into an internal helper), andonTrackinspector events fire with aWatch(<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 indocumentation/*/advanced.md. In debug, callingwatchoutsidebuild()warns (throwsObserverErrorunderstrictMode); inside anObserver/Computed/effectit 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 bothcore.dartandall_observer.dart). EveryComputed/CoreComputed,effect()and worker (ever/once/debounce/interval) created insidescope.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 understrictMode). PlainObservables are deliberately not captured (no resource to release; documented). ScopedObserverMixin: pure-Dart controller counterpart ofObserverStateMixin—scoped(fn),autoDispose(disposer),disposeScope(),isScopeDisposed, and the underlyingscopeexposed.ObserverStateMixininternal refactor: now runs on an internalReactiveScope; 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 understrictMode), matchingReactiveScope.add.- Inspector: new
ObserverInspector.onScopeDispose(ScopeDisposeEvent)event (label +disposedCount), with a no-op default implementation like every other event;RecordingInspectorrecords it,ConsoleInspectorstays silent by default (as withonEffectRun). - Docs: new "Surgical rebuilds with
watch(context)" and "Scoped cleanup withReactiveScope" sections inadvanced.md(EN/PT-BR), updated comparison table (standalone effects, untracked reads, wrapper-less rebuilds, scoped cleanup),migration_from_getx.mdgained a "ReplacingonClosewithScopedObserverMixin" section, and both READMEs show a one-linewatch(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, adebounceworker test usingflutter_test's virtual time, a deterministicObservableFuturetest via injected fakes, and astrictModemisuse 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.mdguide, 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.mdand wiredexample/into CI (flutter testnow 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.mdinto a lean landing page, with the rest moved into topic guides underdocumentation/en/anddocumentation/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 zeropackage:flutterimport and is exposed standalone viapackage:all_observer/core.dart.Observable/Computedare unchanged from the outside — they now wrap this engine internally. - Standalone reactivity:
effect(), plus escape hatchesuntracked(),Observable.peek(), andObservable.previousValue. - Pluggable observability: the
ObserverInspectorinterface (onCreate/onUpdate/onDispose/onTrack/onWarning/onEffectRun), with the classic colored console output now a formalConsoleInspectorimplementation, plus aRecordingInspectorandObserverConfig.inspectors/captureStackTraces. - Async:
ObservableStream<T>, theStreamcounterpart ofObservableFuture, with the same generation-counter race safety.AsyncValue<T>added as an alias forAsyncState<T>. - Lifecycle helpers:
ObserverStateMixin(auto-disposedeffect()s and subscriptions on aState),ObservableStore/Observable.persistWith(optional persistence integration point, e.g. forall_box), andObservableHistory/Observable.withHistory(bounded undo/redo). - Docs:
ARCHITECTURE.mdexpanded to cover all of the above,CONTRIBUTING.mdadded, 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 = xoutside any explicitObservable.batch()— is now automatically routed through the same two-phase fixed-point flush that explicit batches use. Diamond dependency graphs (ComputedA and B both derived from source S,ComputedC depending on A and B) always recompute exactly once, always seeing fully settled upstream values — no glitch, no explicitbatch()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. ThekMaxFlushWaves = 100guard 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 inbatch()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. Usebatch()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):
_flushPendinginbatch_scope.dartnow has a bounded iteration limit (kMaxFlushWaves = 100). A mutual cyclea.listen((v)=>b.value=v+1)+b.listen((v)=>a.value=v+1)inside abatch()previously caused an infinite loop becausekMaxNotificationDepthonly guards nested call-stack recursion, not iterativewhilewaves. The new guard detects the wave overflow, clears pending queues, and reports a descriptive bilingualFlutterErrorinstead of hanging indefinitely. - T1.2 —
previousDatasurvives chainedrun()calls: callingObservableFuture.run()a second time before the first completes previously silently erased the stale value (.valueOrNullreturnednullwhen state was alreadyAsyncLoading). Fixed via_previousDataFromCurrent()helper:AsyncData→ preserve its value;AsyncLoading→ propagate its ownpreviousData;AsyncError→null(documented deliberate choice).
Documentation:
- T1.3 —
Observable.refresh()polymorphic semantics: new bilingual paragraph documenting that subclasses may extendrefresh()beyond a simple notification (e.g.ObservableFuture.refreshre-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>: anObservable<AsyncState<T>>that runs aFuture<T> Function()and tracks its loading/data/error lifecycle. Auto-runs by default (autoStart: true) or viarun()/refresh(). Race-safe: a generation counter discards a stalerun()'s result if a newerrun()started before it resolved, and discards any result that arrives afterclose().AsyncState<T>sealed class:AsyncLoading<T>(withpreviousDatafor stale-while-loading UIs),AsyncData<T>,AsyncError<T>;when/maybeWhen,isLoading/hasData/hasError/valueOrNull, content-based==/hashCode.
Computed:
equalsnamed parameter onComputed's constructor, mirroringObservable's, for custom change-detection (e.g. floating-point tolerance) on the recomputed value.- Diamond-glitch mitigation: inside an active
Observable.batch(), aComputed's recompute is deferred (to the nextvalueread, 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. Outsidebatch, the previous eager-recompute-per-notification behavior is unchanged and documented as a known limitation.
Widgets:
Observer.withChildnamed constructor: rebuilds only the part of the subtree the builder itself constructs, reusing a staticchildwidget across rebuilds instead of reconstructing it — for expensive widgets that don't depend on any observable.
Observable:
Observable.setValue(T newValue): equivalent tovalue = newValue, usable as a tear-off (e.g. directly as anonChangedcallback) and the unambiguous way to assignnullto anObservable<T?>(unlikecall(null), which reads instead of assigns).Observable<T>.select<R>(R Function(T value) selector, {String? name})extension: sugar overComputed(() => selector(value), name: name)for a narrower, memoized derived value. Caller owns and mustclose()the returnedComputed.
Performance:
ListenerRegistrynow backed by aLinkedHashSet<VoidCallback>instead of aList, makingadd/remove/containsO(1) instead of O(n), while preserving insertion order and native deduplication. Seebenchmark/listener_registry_benchmark.dart.
Other:
example/reworked into a multi-page app (bottom navigation) with five focused demos: counter +Computed, debounced search,ObservableFutureasync states,Observable.batchform saving, andObserver/ValueListenableBuilderinterop — each in its own file underexample/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,Computedstaying active after first read untilclose(), single-isolate confinement).
1.0.0 #
Initial release.
Core:
Observable<T>reactive value, plusObservableInt,ObservableDouble,ObservableBool,ObservableString..obsextension for creating observables from literals and collections.ObservableList,ObservableMap,ObservableSetreactive 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 duplicateSetelement,removeWherethat removes nothing,map[k]=vwith an identical value,clear()on an already-empty collection) don't notify at all.Observerwidget with automatic, stack-based dependency tracking and safe rebuild scheduling (no "defunct element" crashes).ObserverValuefor self-contained local reactive state.Computed<T>: lazy, memoized derived values reusing the same dependency tracker asObserver; 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.equalsparameter on theObservable<T>constructor, for custom change-detection (e.g. floating-point tolerance) instead of==.listen/ObservableSubscription, stream-free manual subscriptions.- Workers:
ever,once,debounce,interval, plusWorker/Workers.
Robustness:
- An exception thrown inside one
ever/listencallback does not stop other listeners of the same observable from running; it is reported viaFlutterError.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, andObserverConfig.strictModeturns this case into a thrownObserverErroras well as the "empty Observer" case. listen()/close()on an already-close()dObservable/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
ObserverConfigand non-fatal misuse warnings (with optionalstrictMode). - Zero external dependencies; full
ValueListenableinteroperability.
See AUDIT_REPORT.md at the repository root for the full item-by-item performance/robustness audit this release was validated against.