dartfier 0.1.0
dartfier: ^0.1.0 copied to clipboard
Fine-grained reactivity for Flutter, built on ChangeNotifier: Signal, Computed, Effect, AsyncSignal and an auto-tracking Observer widget. Zero dependencies.
Changelog #
0.1.0 - 2026-08-01 #
Added #
Signal<T>— reactive value onChangeNotifierimplementingValueListenable, with an equality write guard and customequals.Computed<T>— lazy derived value with automatic dependency tracking, cycle detection and identity-preservingequals. A write settles in two phases — staleness across the graph first, notifications last — so a recomputation landing on an equal value notifies nobody and stops the cascade there, while aComputednobody reads or listens to is never recomputed by a write — subscribing to one runs its first computation, so a listener added before any read is still connected to the graph.ReadonlySignal<T>— read-only supertype ofSignalandComputed: reading and listening without writing. The interface isabstract finaland the concrete classes arebase— implementing them externally is a compile error (extending stays possible; the subclass must be markedbase,finalorsealed), so aReadonlySignalis never a foreign notifier.Effect— reactive side effect with microtask-coalesced reruns and anonCleanupregistrar, where a cleanup that throws is reported toFlutterErrorinstead of aborting the remaining cleanups, so neither a rerun nor adisposecan leave a resource behind; a runaway effect whose reruns keep writing to a signal it reads disposes itself and throws aStateErrorafter 100 consecutive self-triggered reruns, instead of silently starving the event loop.Observer— auto-tracking widget, plusObserver.withChildfor static subtrees,listenablesfor foreignListenables (aFocusNode, a text controller) observed alongside the tracked reads, and debug errors when the builder tracks nothing or writes to a signal it tracks.Signal.update()— derives the next value from the current one, read withpeek(), keeping the equality guard of the setter.listen()on anyReadonlySignal— side effect with the previous and the next value, built onEffect: coalesced per microtask, silent on spuriousComputednotifications, untracked callback, returning theEffectas the subscription handle.Watch<T>— side effect on an auto-tracked expression, skipping the first run: the tracking function may read as many reactive values as it needs (a record covers several at once, comparing structurally), and the callback receives the previous and the next result. Reruns inherit the guarantees ofEffect— coalesced per microtask, untracked callback — and a customequalscovers a tracking function that returns a collection. It is thelisten()of an expression instead of a single signal, with a singledispose().when()on anyReadonlySignal— aFuturecompleting with the first value that satisfies a condition, checking the current value first and subscribing only when needed.untracked()andpeek()escape hatches for reading without subscribing.batch()— groups several writes into one atomic update: listeners are notified once, at the end, so the half-applied state a group passes through never escapes, and aComputedreconciles once per batch instead of once per write. The equality guard covers the group — a batch that moves a value and puts it back notifies nobody. Reads inside the block stay current, batches nest, and pending notifications are delivered even when the body throws.- Runaway propagation guard — a listener that writes back to the value it was notified about settles in the same propagation, but one that never converges throws a
StateErrorafter 100 rounds naming the likely cause, instead of hanging the isolate with no error and no stack. - A
Computedwhose function throws no longer silences the nodes reconciled after it: the rest of the graph still settles and notifies, the first exception surfaces from the write, and any further one is reported toFlutterError. Notification no longer depends on the order the computeds were declared in. - Notifications always run untracked — a listener that reads a
SignalorComputedno longer reports that read to the tracked scope that triggered the write, so anEffectwriting from its body never subscribes to what its listeners happen to read. AsyncValue<T>andAsyncSignal<T>— asynchronous state as a synchronous reactive value: idle/loading/data/error with previous data carried through reloads, latest-winsrunand debug-friendlytoString; a fresh signal startsAsyncIdle.