Store API reference¶
Store<State, Action, Effect> is the public contract for every running state machine instance,
regardless of how it was created (store { }, StateMachineStore, etc.).
Properties¶
id: String¶
A unique identifier for this store instance. Auto-generated as a UUID by default. Used by
StoreRegistry to distinguish multiple instances of the same store class, and by
RelayScope.dispatch(…, id = …) to target a specific instance:
// Target one specific CartStore instance out of several registered:
dispatch(CartStore::class, CartAction.Clear, id = specificCartId)
You can read the id to log or correlate store activity:
install(object : Plugin<MyState, MyAction, MyEffect> {
override fun onTransition(fromState: MyState, toState: MyState) {
logger.d("store[$id] $fromState → $toState")
}
})
state: StateFlow<State>¶
The current state, exposed as a StateFlow. Always holds a value; the initial emission is the
configured initialState.
Collecting state also calls start() implicitly — the store's onEnter for the initial state
fires the first time a subscriber attaches.
effects: SharedFlow<Effect>¶
One-shot side effects, exposed as a SharedFlow with replay = 0. Late subscribers miss effects
emitted before they subscribed. Use handleEffects { } (see
Compose integration) or attach your collector before the first dispatch
to avoid missing emissions.
actions: SharedFlow<Action>¶
Every action dispatched to the store, emitted in dispatch order before the action is
processed. replay = 0 — late subscribers miss past actions.
Primary use-case is relaying: the relay { action<A> { … } } DSL subscribes to this flow
internally. You can also use it for debug logging or analytics:
isActive: Boolean¶
true while the store is processing actions; false after stop() is called or the owning
CoroutineScope is cancelled. All write operations (dispatch, onLifecycleEvent) are silent
no-ops when isActive is false.
Functions¶
dispatch(action: Action)¶
Enqueue an action for processing. Non-suspending and safe to call from any thread or coroutine. Actions are processed sequentially in the order they are enqueued.
start()¶
Fire the onEnter hook for the initial state, if one is registered.
When an initializer was provided at store construction, start() enqueues the async restore
first. The initializer runs inside the processing coroutine before onEnter and before any
queued actions, so the machine always sees the restored state as its first state.
start() is called automatically the first time a subscriber collects state, actions, or
effects. Call it explicitly when you need onEnter to fire before any collector attaches —
for example, in a background ViewModel that starts work immediately on creation:
class SyncViewModel : ViewModel() {
val store = store<SyncState, SyncAction, SyncEffect>(viewModelScope) {
initialState(SyncState.Idle)
state<SyncState.Idle> {
onEnter { dispatch(SyncAction.StartSync) }
}
}
init {
store.start() // onEnter fires immediately; no UI subscriber needed
}
}
Calling start() more than once is a safe no-op. Calling it after stop() is also a no-op.
stop()¶
Stop the store permanently. Cancels the internal processing coroutine and all running keyed jobs.
Closes the trigger channel. All subsequent calls to dispatch and onLifecycleEvent become
silent no-ops.
Note that stop() does not fire callbacks registered via invokeOnCompletion — those are
attached to the owning CoroutineScope and only fire when the scope is cancelled. If you stop a
store early (before its scope is cancelled) and the store is registered in a StoreRegistry, call
unregister manually:
// Composition entry leaving the nav stack
DisposableEffect(viewModel) {
onDispose {
viewModel.store.stop()
registry.unregister(viewModel.store)
}
}
On Android, prefer letting the owning CoroutineScope (e.g. viewModelScope) stop the store
automatically when the ViewModel is cleared — scope cancellation triggers invokeOnCompletion and
auto-unregistration. Call stop() explicitly only when the store has a shorter lifetime than its
scope.
onLifecycleEvent(event: LifecycleEvent)¶
Forward an application lifecycle event into the machine. The event is enqueued in the same channel as actions and processed sequentially. See Lifecycle hooks for the full list of events and how to react to them in the DSL.
triggerStateHook(hook: StateHook<State>)¶
Fire a state lifecycle hook (OnEnter, OnExit, or OnUpdate) directly, without requiring a
transition. Annotated @InternalMonakaApi — calling it outside of test infrastructure requires
@OptIn(InternalMonakaApi::class). In practice this is handled automatically by :monaka-test,
which calls it on your behalf via trigger(StateHook.OnEnter) { … }. See
Testing for usage.
invokeOnCompletion(handler: (Throwable?) -> Unit): DisposableHandle¶
Register a callback that fires when the store's owning CoroutineScope is cancelled. The handler
receives the cancellation cause, or null for normal completion. Useful for observing store
lifetime without holding a reference to the underlying scope:
val handle = store.invokeOnCompletion { cause ->
logger.d("store completed, cause=$cause")
}
// Later, if you want to remove the callback:
handle.dispose()
The callback fires when the store's owning CoroutineScope is cancelled (e.g. viewModelScope
cleared on Android). It does not fire when stop() is called directly — stop() only
cancels the internal processing coroutine, not the scope.
This is how StoreRegistry.register implements auto-unregistration: it attaches an
invokeOnCompletion handler that calls stop() and unregister when the owning scope is
cancelled. If you call stop() directly before the scope is cancelled, call
registry.unregister(store) manually to remove it from the registry.