StateMachine

repository·main·Indexed 24 days ago

https://github.com/tinder/statemachine

A lightweight, composable state machine library for Kotlin and Swift (version 0.3.0). It provides a type-safe DSL to define explicit states, events, and side effects, featuring thread-safe transitions, lifecycle listeners (onEnter/onExit), and flexible pattern matching via the Matcher class.

Tokens
1.6K
Snippets
3
Records
11
Agent score
35%

What's inside StateMachine

  1. Use StateMachine in Kotlin

    main

    To use StateMachine in Kotlin, define your State, Event, and SideEffect as sealed classes. Use the StateMachine.create<State, Event, SideEffect> DSL to initialize the machine, define the initialState, declare state-specific transitions using state<T> { on<E> { ... } }, and handle side effects via onTransition.

    Transitions are performed using stateMachine.transition(event) which returns a StateMachine.Transition.Valid object if the transition is successful.

    // 1. Define types
    sealed class State {
        object Solid : State()
        object Liquid : State()
    }
    
    sealed class Event {
        object OnMelted : Event()
    }
    
    sealed class SideEffect {
        object LogMelted : SideEffect()
    }
    
    // 2. Initialize
    val stateMachine = StateMachine.create<State, Event, SideEffect> {
        initialState(State.Solid)
        state<State.Solid> {
            on<Event.OnMelted> {
                transitionTo(State.Liquid, SideEffect.LogMelted)
            }
        }
        onTransition {
            val validTransition = it as? StateMachine.Transition.Valid ?: return@onTransition
            when (validTransition.sideEffect) {
                SideEffect.LogMelted -> logger.log("Melted")
            }
        }
    }
    
    // 3. Perform transition
    val transition = stateMachine.transition(Event.OnMelted)
  2. Install StateMachine in Kotlin

    main

    You can add StateMachine to your Kotlin project using Maven or Gradle. The current version is 0.3.0.

    ### Maven
    
    ```xml
    <dependency>
        <groupId>com.tinder.statemachine</groupId>
        <artifactId>statemachine</artifactId>
        <version>0.3.0</version>
    </dependency>

    Gradle

    implementation 'com.tinder.statemachine:statemachine:0.3.0'
  3. Use StateMachine in Swift

    main

    To use StateMachine in Swift, adopt the StateMachineBuilder protocol. Define your State and Event using enums marked with @StateMachineHashable.

    Initialize the machine using the StateMachine<State, Event, SideEffect> DSL. Transitions are triggered via try stateMachine.transition(.event). Side effects are handled within the onTransition block by checking for the .success case of the transition result.

  4. Handle Swift Enumerations with Associated Values (Pre-Swift 5.9)

    main
    For Swift versions older than 5.9, if your State or Event enums use associated values, you must manually implement StateMachineHashable conformance. It is recommended to use Sourcery with the AutoStateMachineHashable stencil template provided in this repository to generate this boilerplate.
  5. Trigger a state transition with transition()

    main

    To move the state machine from its current state to a new state, call transition(event: EVENT).

    This method is thread-safe and performs the following:

    1. Checks if the current state has a valid transition for the provided event.
    2. If valid, updates the internal state to the new state.
    3. Triggers onExit listeners for the old state.
    4. Triggers onEnter listeners for the new state.
    5. Triggers global onTransition listeners.

    It returns a Transition object which can be inspected to see if the transition was Valid or Invalid.

  6. Create a StateMachine using create()

    main

    Initialize a new StateMachine by providing a configuration block to the create factory method. You use a GraphBuilder to define the initial state, the states available in your machine, and global transition listeners.

    Key components of the configuration:

    • initialState(state): Sets the starting state.
    • state(matcher) { ... }: Defines a state and its behavior.
    • onTransition { ... }: Registers a global listener for every transition attempt.
  7. Define state transitions and side effects

    main

    Within a state definition block, use the on method to specify how the machine should react to specific events.

    When an event matches, you must return a TransitionTo object using either transitionTo(newState, sideEffect) or dontTransition(sideEffect).

    Available on variants:

    • on<E>(matcher) { ... }: Matches an event based on a Matcher.
    • on(event) { ... }: Matches a specific event instance.
    • on<E> { ... }: Matches any event of type E.
  8. Inspect Transition results

    main

    The transition() method returns a Transition sealed class. You should check the type to determine if the event was handled.

    • Transition.Valid: The event matched a defined transition. Contains fromState, event, toState, and an optional sideEffect.
    • Transition.Invalid: No transition was defined for the given event in the current state. Contains fromState and event.