Skip to content

Runtime boundaries and adapter APIs ​

Use these APIs when building integrations, diagnosing behavior, or maintaining generated code. Application stores normally need only the higher-level primitives.

Actions, transactions, and untracked work ​

runInAction and Action provide an action boundary: writes are allowed according to policy, tracking is suspended within the action, and dependent reactions flush after the outer batch ends. transaction groups notifications but is not a substitute for action write permissions. untracked prevents reads from becoming dependencies; it does not make writes transactional.

Always balance low-level startBatch / endBatch and tracking/policy push/pop operations in try/finally. Prefer the scoped helpers. An action is not rollback: if its body throws after a write, that write remains.

ActionController, ActionRunInfo, and ConditionalAction support generated wrappers and internal action bookkeeping. Avoid storing a run token for reuse across unrelated calls. AsyncAction owns its asynchronous execution context; it does not provide mutual exclusion.

Build an observable adapter ​

An Atom is a dependency signal without its own application value:

dart
class Reading {
  final atom = Atom(name: 'Reading');
  int _value = 0;
  int get value { atom.reportObserved(); return _value; }
  void update(int value) => runInAction(() {
    if (_value == value) return;
    _value = value;
    atom.reportChanged();
  });
}

Lifecycle hooks can start external observation when the atom becomes observed and stop it when unused. AtomSpyReporter.reportRead and reportWrite add policy/equality/spy integration used by generated accessors. A custom adapter must report every meaningful change and own external-resource cleanup.

ObservableValue is the read-only value contract implemented by observable values. Derivation is exposed for runtime integration but includes library-private machinery: it is not a convenient external subclassing API. Prefer Computed and reactions.

Contexts are separate reactive runtimes ​

mainContext is the default. createContext and ReactiveContext allow isolated configuration. Construct observables, actions, and reactions with the same context; reading across contexts does not establish a reliable shared dependency graph.

ReactiveConfig configures read/write policies, reaction error boundaries, and the stabilization limit. ReactiveReadPolicy and ReactiveWritePolicy define enforcement choices. Context methods that manipulate tracking, pending reactions, or computation depth are runtime machinery; incorrect balancing can corrupt subsequent work.

Notifications and interception ​

Listenable, Listener, and Listeners describe low-level observation. Interceptable, Interceptor, and Interceptors can inspect or replace a proposed change, or reject it by returning null where supported. WillChangeNotification represents a proposal; ChangeNotification represents the committed result, classified by OperationType.

dart
final score = Observable(0);
final stop = score.intercept((change) => change.newValue! < 0 ? null : change);
runInAction(() => score.value = -1); // Rejected by the interceptor.
stop();

Use action-level validation for ordinary business rules. Interception is a lower-level mechanism with ordering and reentrancy consequences. Observe the committed value, not an assumption about the original proposal.

Diagnostics have lifetimes too ​

context.spy returns a Dispose callback. SpyListener receives SpyEvent subclasses for action, observable, computed, reaction, disposal, error, and end events. Enable this only when needed: event construction and serialization add work.

dart
final stop = mainContext.spy((event) => print(event.type));
// Perform the operation being diagnosed.
stop();

onReactionError installs a ReactionErrorHandler and returns a disposer. Per-reaction onError can handle local errors. MobXCaughtException retains an original exception and stack; MobXCyclicReactionException identifies a graph that did not stabilize; MobXException is the common error type. Reporting errors must not itself create a write loop.

Reaction exposes tracking and lifecycle functionality for adapters; ReactionDisposer is the callable owner handle returned by effect helpers. Dispose it once its owner is finished. Use when for a cancellable one-shot effect, and asyncWhen for awaiting a condition with an appropriate timeout.

Made with care for the Dart & Flutter community. MIT licensed.