orbital_state 0.3.0 copy "orbital_state: ^0.3.0" to clipboard
orbital_state: ^0.3.0 copied to clipboard

Reactive state mixin and UI helpers for Orbital lifecycle objects.

orbital_state #

Reactive state mixin for OrbitalLifecycle objects in the Orbital ecosystem.

What this package contains #

orbital_state provides StateMixin<T>, a small mixin that adds reactive state to any OrbitalLifecycle controller (as used by orbital_injector and orbital_router), without requiring an external state manager.

It handles:

  • lazily creating the backing notifier on the first emit
  • rebuilding widgets via watch whenever state changes
  • subscribing other lifecycle owners via listen, with automatic cleanup through addDisposer when the owner is disposed, plus a cancellation handle for stopping earlier
  • disposing the underlying notifier from onDispose

The framework does not define loading/error/empty states — model T as you need (sealed class, enum, plain class, etc.).

Quick start #

class CounterController extends OrbitalLifecycle with StateMixin<int> {
  @override
  FutureOr<void> onInit() {
    emit(0);
  }

  void increment() => emit(state + 1);
}

// In a widget:
controller.watch((count) => Text('count:$count'));

emit drops equal values #

emit compares the new value with the current one using ==. When they are equal it keeps the previously stored instance and notifies nobody: watch does not rebuild and listen callbacks do not run. The usual trap is a mutable state object — mutating it in place and re-emitting the same instance is always a no-op, because an object is equal to itself:

state.items.add(item); // mutation in place
emit(state);           // no-op: nothing rebuilds

Prefer immutable state and emit a new value (emit(state.copyWith(...))). When that is not possible, emit(value, force: true) notifies unconditionally and stores value even when it compares equal.

watch vs listen #

  • watch returns a widget that rebuilds itself whenever emit changes state.
  • listen subscribes an OrbitalLifecycle owner to another controller's state changes with a plain callback, and automatically removes the subscription when the owner is disposed. Pass fireImmediately: true to invoke the callback once with the current state when registering.

listen returns an OrbitalStateSubscription; call cancel() to stop listening before the owner is disposed. Cancelling is idempotent. Registering the same owner and callback again is a no-op that returns the existing handle, so listeners and disposers never stack — but note that two closure literals are never equal, so pass a method tear-off (this.onValue) when deduplication matters.

Both require at least one prior emit — emit the initial state in onInit before calling watch or listen. Violating that throws OrbitalStateNotEmittedException, in release builds too.

After disposal #

Once the owning controller has been disposed, the state API degrades safely:

  • emit is a no-op (even with force), so an in-flight async callback that resolves after the page was popped cannot touch a disposed notifier
  • watch renders the builder once with the last known state instead of attaching a listener
  • listen returns an already-cancelled handle: no listener is attached and no disposer is registered, even with fireImmediately
  • onDispose is idempotent — calling it twice (or having both the owning container and a caller dispose) disposes the notifier exactly once, and disposing the subject from inside one of its own listeners during a notification round is deferred safely instead of tripping a ChangeNotifier assertion

More detail #

See doc.md for the design rationale and lifecycle interaction with orbital_core and orbital_injector.

0
likes
150
points
306
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Reactive state mixin and UI helpers for Orbital lifecycle objects.

Repository (GitHub)

License

MIT (license)

Dependencies

flutter, orbital_core

More

Packages that depend on orbital_state