Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
StateMachine
https://github.com/tinder/statemachine
Admin
StateMachine is a library that provides state machine functionality implemented in both Kotlin and
...
Tokens:
6,240
Snippets:
39
Trust Score:
7.7
Update:
4 months ago
Context
Skills
Chat
Benchmark
85.5
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# StateMachine StateMachine is a cross-platform library for implementing type-safe finite state machines in Kotlin and Swift. The library provides a declarative DSL for defining states, events, and transitions, making it easy to model complex state-based logic with compile-time safety. Originally developed by Tinder (Match Group), StateMachine is used in production applications like Scarlet and provides a composable pattern for pure state machines with side effects. The library handles state transitions atomically with thread-safe state management, supports side effects for each transition, and allows registering listeners for state changes and lifecycle events. Both Kotlin and Swift implementations follow platform-specific idioms while maintaining API consistency. The DSL-based approach enables clear, maintainable code that maps directly to state diagrams, reducing bugs in state-dependent behavior. ## Creating a Basic State Machine in Kotlin Define states, events, and side effects, then create a state machine with transition rules. ```kotlin // Define sealed classes for type safety sealed class State { object Solid : State() object Liquid : State() object Gas : State() } sealed class Event { object OnMelted : Event() object OnFroze : Event() object OnVaporized : Event() object OnCondensed : Event() } sealed class SideEffect { object LogMelted : SideEffect() object LogFrozen : SideEffect() object LogVaporized : SideEffect() object LogCondensed : SideEffect() } // Create the state machine val stateMachine = StateMachine.create<State, Event, SideEffect> { initialState(State.Solid) state<State.Solid> { on<Event.OnMelted> { transitionTo(State.Liquid, SideEffect.LogMelted) } } state<State.Liquid> { on<Event.OnFroze> { transitionTo(State.Solid, SideEffect.LogFrozen) } on<Event.OnVaporized> { transitionTo(State.Gas, SideEffect.LogVaporized) } } state<State.Gas> { on<Event.OnCondensed> { transitionTo(State.Liquid, SideEffect.LogCondensed) } } onTransition { val validTransition = it as? StateMachine.Transition.Valid ?: return@onTransition when (validTransition.sideEffect) { SideEffect.LogMelted -> println("Matter melted") SideEffect.LogFrozen -> println("Matter froze") SideEffect.LogVaporized -> println("Matter vaporized") SideEffect.LogCondensed -> println("Matter condensed") } } } // Use the state machine println(stateMachine.state) // Output: Solid val transition = stateMachine.transition(Event.OnMelted) // Console output: "Matter melted" println(stateMachine.state) // Output: Liquid println(transition) // Output: Valid(fromState=Solid, event=OnMelted, toState=Liquid, sideEffect=LogMelted) ``` ## Creating a State Machine in Swift Define states and events with the StateMachineHashable macro, then create a state machine using the builder DSL. ```swift // Define enumerations with @StateMachineHashable macro @StateMachineHashable enum State { case solid, liquid, gas } @StateMachineHashable enum Event { case melt, freeze, vaporize, condense } enum SideEffect { case logMelted, logFrozen, logVaporized, logCondensed } // Create the state machine let stateMachine = StateMachine<State, Event, SideEffect> { initialState(.solid) state(.solid) { on(.melt) { transition(to: .liquid, emit: .logMelted) } } state(.liquid) { on(.freeze) { transition(to: .solid, emit: .logFrozen) } on(.vaporize) { transition(to: .gas, emit: .logVaporized) } } state(.gas) { on(.condense) { transition(to: .liquid, emit: .logCondensed) } } onTransition { guard case let .success(transition) = $0, let sideEffect = transition.sideEffect else { return } switch sideEffect { case .logMelted: print("Matter melted") case .logFrozen: print("Matter froze") case .logVaporized: print("Matter vaporized") case .logCondensed: print("Matter condensed") } } } // Use the state machine print(stateMachine.state) // Output: solid let transition = try stateMachine.transition(.melt) // Console output: "Matter melted" print(stateMachine.state) // Output: liquid print(transition) // Output: Valid(fromState: solid, event: melt, toState: liquid, sideEffect: logMelted) ``` ## State Machine with Associated Values (Kotlin) Handle states with data using data classes and conditional transitions based on state properties. ```kotlin sealed class State { data class Locked(val credit: Int) : State() object Unlocked : State() data class Broken(val oldState: State) : State() } sealed class Event { data class InsertCoin(val value: Int) : Event() object AdmitPerson : Event() object MachineDidFail : Event() object MachineRepairDidComplete : Event() } sealed class Command { object SoundAlarm : Command() object CloseDoors : Command() object OpenDoors : Command() object OrderRepair : Command() } val FARE_PRICE = 50 val turnstile = StateMachine.create<State, Event, Command> { initialState(State.Locked(credit = 0)) state<State.Locked> { on<Event.InsertCoin> { val newCredit = credit + it.value if (newCredit >= FARE_PRICE) { transitionTo(State.Unlocked, Command.OpenDoors) } else { transitionTo(State.Locked(newCredit)) } } on<Event.AdmitPerson> { dontTransition(Command.SoundAlarm) } on<Event.MachineDidFail> { transitionTo(State.Broken(this), Command.OrderRepair) } } state<State.Unlocked> { on<Event.AdmitPerson> { transitionTo(State.Locked(credit = 0), Command.CloseDoors) } } state<State.Broken> { on<Event.MachineRepairDidComplete> { transitionTo(oldState) } } } // Usage example println(turnstile.state) // Output: Locked(credit=0) turnstile.transition(Event.InsertCoin(10)) println(turnstile.state) // Output: Locked(credit=10) turnstile.transition(Event.InsertCoin(45)) println(turnstile.state) // Output: Unlocked turnstile.transition(Event.AdmitPerson) println(turnstile.state) // Output: Locked(credit=0) ``` ## State Lifecycle Listeners (Kotlin) Register callbacks for state entry and exit events to perform actions during transitions. ```kotlin val stateMachine = StateMachine.create<State, Event, SideEffect> { initialState(State.Solid) state<State.Solid> { onEnter { cause -> println("Entered solid state due to event: $cause") } onExit { cause -> println("Exiting solid state due to event: $cause") } on<Event.OnMelted> { transitionTo(State.Liquid) } } state<State.Liquid> { onEnter { cause -> println("Entered liquid state due to event: $cause") } on<Event.OnFroze> { transitionTo(State.Solid) } } } // Transition triggers lifecycle callbacks stateMachine.transition(Event.OnMelted) // Output: // "Exiting solid state due to event: OnMelted" // "Entered liquid state due to event: OnMelted" stateMachine.transition(Event.OnFroze) // Output: // "Exiting liquid state due to event: OnFroze" // "Entered solid state due to event: OnFroze" ``` ## Transition Observers (Swift) Observe state transitions by registering callbacks that receive transition results. ```swift class TransitionLogger { let stateMachine = StateMachine<State, Event, SideEffect> { initialState(.solid) state(.solid) { on(.melt) { transition(to: .liquid, emit: .logMelted) } } state(.liquid) { on(.freeze) { transition(to: .solid, emit: .logFrozen) } } } func setupObserver() { stateMachine.startObserving(self) { result in switch result { case .success(let transition): print("Transitioned from \(transition.fromState) to \(transition.toState)") if let effect = transition.sideEffect { print("Side effect: \(effect)") } case .failure(let error): print("Transition failed: \(error)") } } } } let logger = TransitionLogger() logger.setupObserver() try logger.stateMachine.transition(.melt) // Output: // "Transitioned from solid to liquid" // "Side effect: logMelted" // Stop observing when done logger.stateMachine.stopObserving(logger) ``` ## Modifying State Machine State (Kotlin) Create a new state machine instance with modified initial state while preserving transition rules. ```kotlin val originalMachine = StateMachine.create<State, Event, SideEffect> { initialState(State.Solid) state<State.Solid> { on<Event.OnMelted> { transitionTo(State.Liquid, SideEffect.LogMelted) } } state<State.Liquid> { on<Event.OnFroze> { transitionTo(State.Solid, SideEffect.LogFrozen) } } } println(originalMachine.state) // Output: Solid // Create a new machine starting from a different state val modifiedMachine = originalMachine.with { initialState(State.Liquid) } println(modifiedMachine.state) // Output: Liquid // Original machine is unchanged println(originalMachine.state) // Output: Solid // Transition rules are preserved val transition = modifiedMachine.transition(Event.OnFroze) println(modifiedMachine.state) // Output: Solid ``` ## Handling Invalid Transitions (Kotlin) Handle cases where events are not valid for the current state by checking transition results. ```kotlin val stateMachine = StateMachine.create<State, Event, SideEffect> { initialState(State.Solid) state<State.Solid> { on<Event.OnMelted> { transitionTo(State.Liquid) } } state<State.Liquid> { on<Event.OnVaporized> { transitionTo(State.Gas) } } } println(stateMachine.state) // Output: Solid // Valid transition val validTransition = stateMachine.transition(Event.OnMelted) when (validTransition) { is StateMachine.Transition.Valid -> { println("Success: ${validTransition.fromState} -> ${validTransition.toState}") } is StateMachine.Transition.Invalid -> { println("Invalid transition from ${validTransition.fromState} with event ${validTransition.event}") } } // Output: "Success: Solid -> Liquid" // Invalid transition (OnFroze not defined for Liquid state) val invalidTransition = stateMachine.transition(Event.OnFroze) when (invalidTransition) { is StateMachine.Transition.Valid -> { println("Success: ${invalidTransition.fromState} -> ${invalidTransition.toState}") } is StateMachine.Transition.Invalid -> { println("Invalid transition from ${invalidTransition.fromState} with event ${invalidTransition.event}") } } // Output: "Invalid transition from Liquid with event OnFroze" // State remains unchanged after invalid transition println(stateMachine.state) // Output: Liquid ``` ## Error Handling with Result Types (Swift) Use Swift's Result type to handle invalid transitions and errors gracefully. ```swift let stateMachine = StateMachine<State, Event, SideEffect> { initialState(.solid) state(.solid) { on(.melt) { transition(to: .liquid) } } state(.liquid) { on(.vaporize) { transition(to: .gas) } } } print(stateMachine.state) // Output: solid // Valid transition using try do { let transition = try stateMachine.transition(.melt) print("Success: \(transition.fromState) -> \(transition.toState)") // Output: "Success: solid -> liquid" } catch { print("Transition failed: \(error)") } // Invalid transition (freeze not defined for liquid state) do { let transition = try stateMachine.transition(.freeze) print("Success: \(transition.fromState) -> \(transition.toState)") } catch StateMachine<State, Event, SideEffect>.Transition.Invalid() { print("Invalid transition error") // Output: "Invalid transition error" } catch { print("Other error: \(error)") } print(stateMachine.state) // Output: liquid (unchanged) ``` ## Installation with Gradle (Kotlin) Add StateMachine to a Kotlin project using Gradle with Maven Central. ```groovy // build.gradle repositories { mavenCentral() } dependencies { implementation 'com.tinder.statemachine:statemachine:0.3.0' } ``` ## Installation with Swift Package Manager Add StateMachine to a Swift project using Swift Package Manager. ```swift // Package.swift // swift-tools-version:5.9 import PackageDescription let package = Package( name: "MyApp", dependencies: [ .package(url: "https://github.com/Tinder/StateMachine.git", from: "0.3.0") ], targets: [ .target( name: "MyApp", dependencies: ["StateMachine"] ) ] ) ``` ## Installation with CocoaPods (Swift) Add StateMachine to an iOS project using CocoaPods. ```ruby # Podfile platform :ios, '13.0' use_frameworks! target 'MyApp' do pod 'StateMachine', :git => 'https://github.com/Tinder/StateMachine.git' end ``` StateMachine excels at modeling complex business logic with clear state transitions, making it ideal for workflow management, UI state handling, network connection management, and game state control. The library's type-safe approach catches errors at compile time, and the DSL syntax creates self-documenting code that matches state diagrams. Common use cases include WebSocket connection state management (as seen in Scarlet), authentication flows, payment processing, and any scenario where an entity transitions between well-defined states in response to events. Integration is straightforward with dependency injection frameworks, reactive programming libraries, and architectural patterns like MVVM or Clean Architecture. The state machine can be observed reactively, and side effects provide clean extension points for logging, analytics, or triggering external actions. Thread-safe state transitions ensure reliable behavior in concurrent environments, while the ability to create modified instances with `with()` in Kotlin supports testing and state restoration scenarios.