Events

Page contents

All events that can be processed by the library are subclassed from Event class. User defined events often contain properties that serve like data inputs for your StateMachine.

Conceptually events notify a StateMachine about outside world changes. Generally it is not recommended to confuse them with commands, see do not section for details.

Event processing

When a StateMachine is created, configured and started it is ready to process incoming events. It is done with processEvent() functions family.

  • processEvent() - suspendable version, synchronous
  • processEventBlocking() - blocking version. Not suspendable, synchronous. Uses kotlinx.coroutines.runBlocking internally if you use StateMachine with coroutines support. Or just runs code in-place for StdLib StateMachine instance.
  • processEventByLaunch() - (available in kstatemachine-coroutines artifact) Not suspendable, asynchronous, uses StateMachine’s CoroutineScope to process event in a new coroutine by kotlinx.coroutines.launch function. Cannot be used with StdLib StateMachine instance (throws in this case).
  • processEventByAsync() - (available in kstatemachine-coroutines artifact) Not suspendable, asynchronous, uses StateMachine’s CoroutineScope to process event in a new coroutine by kotlinx.coroutines.async function. Returnskotlinx.coroutines.Deferred with ProcessingResult. Cannot be used with StdLib StateMachine instance (throws in this case).

All processEvent variants return or resolve to ProcessingResult:

Value Meaning
PROCESSED A matching transition was found and triggered
IGNORED No matching transition found; event was passed to IgnoredEventHandler
PENDING Another event is currently being processed; this event was queued or dropped depending on PendingEventHandler

Choosing a processEvent variant

Variant Suspends Returns When to use
processEvent() yes ProcessingResult Default choice from coroutine code
processEventBlocking() no ProcessingResult Non-coroutine context; never call from a listener callback (deadlock risk)
processEventByLaunch() no Unit Fire-and-forget; you do not need the result
processEventByAsync() no Deferred<ProcessingResult> Non-suspending dispatch when you need to check the result later

processEventByLaunch and processEventByAsync are only available in the kstatemachine-coroutines artifact and cannot be used with a StdLib machine instance. See Coroutines artifact for code examples.

Ignored events

By default, state machine simply ignores events that does not match any defined transition. You can see those events if logging is enabled or use custom IgnoredEventHandler for example to throw error:

createStateMachine(scope) {
    // ...
    ignoredEventHandler = StateMachine.IgnoredEventHandler {
        error("unexpected ${it.event}")
    }
}

The library provides implementation of such throwing handler by throwingIgnoredEventHandler() function.

Pending events

Pending events are such events that are posted for processing while another event is already processing, for example from listeners callbacks.

Default PendingEventHandler that is created with queuePendingEventHandler() stores such events in queue and machine processes them all after a current one. This allows to call processEvent() from listeners callbacks.

If you are using another PendingEventHandler implementation, like logging or throwing one created by throwingPendingEventHandler() function, then behaviour of calling processEvent() while state machine is already processing event will depend on PendingEventHandler implementation. Pending event may be simply dropped or exception thrown. Alternatively with custom PendingEventHandler you can post such events to some queue to process them later passing to processEvent(). Using of throwing PendingEventHandler sample:

createStateMachine(scope) {
    // ...
    pendingEventHandler = throwingPendingEventHandler()
}

PendingEventHandler that does nothing will not let you process pending events (they will be dropped) as it leads to undefined machine state and mixed notifications.

Event argument

Usually if event may hold some data we define Event subclass, it is type safe. Sometimes if data is optional it may be simpler to use event argument. You can specify arbitrary argument with an event in processEvent() function. Then you can get this argument in a state and transition listeners.

val machine = createStateMachine(scope) {
    state("offState").onEntry {
        println("Event ${it.event} argument: ${it.argument}")
    }
    // ...
}
// Pass argument with event
machine.processEvent(TurnOn, 42)

Type of arguments is Any?, so it is not type safe to use them.

Do not

State machine is a powerful tool to control states, so let it do its job. Do not try to rule it from outside (selecting a target state) by sending different event types depending on business logic state. Let the state machine to make decisions itself.

Wrong - managing target state from outside:

if (somethingHappend)
    machine.processEvent(GoToState1Event)
else
    machine.processEvent(GoToState2Event)

Correct - let the state machine to make decisions on an event:

machine.processEvent(SomethingHappenedEvent)

In certain scenarios (like a state pattern maybe) it is fine to use events like some kind of setState() / goToState() functions but in general it is wrong, as events are not commands.


This site uses Just the Docs, a documentation theme for Jekyll.