A Kotlin and Swift DSL for finite state machine

Last update: Jun 22, 2022

StateMachine

CircleCI

A state machine library in Kotlin and Swift.

StateMachine is used in Scarlet

Example State Diagram

The examples below create a StateMachine from the following state diagram for matter:

Kotlin Usage

Define states, events and side effects:

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()
}

Initialize state machine and declare state transitions:

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 -> logger.log(ON_MELTED_MESSAGE)
            SideEffect.LogFrozen -> logger.log(ON_FROZEN_MESSAGE)
            SideEffect.LogVaporized -> logger.log(ON_VAPORIZED_MESSAGE)
            SideEffect.LogCondensed -> logger.log(ON_CONDENSED_MESSAGE)
        }
    }
}

Perform state transitions:

assertThat(stateMachine.state).isEqualTo(Solid)

// When
val transition = stateMachine.transition(OnMelted)

// Then
assertThat(stateMachine.state).isEqualTo(Liquid)
assertThat(transition).isEqualTo(
    StateMachine.Transition.Valid(Solid, OnMelted, Liquid, LogMelted)
)
then(logger).should().log(ON_MELTED_MESSAGE)

Swift Usage

Adopt StateMachineBuilder to gain access to the DSL builder methods:

class MyExample: StateMachineBuilder {

    ... state machine implementation ...
}

Define states, events and side effects:

enum State: StateMachineHashable {
    case solid, liquid, gas
}

enum Event: StateMachineHashable {
    case melt, freeze, vaporize, condense
}

enum SideEffect {
    case logMelted, logFrozen, logVaporized, logCondensed
}

Initialize state machine and declare state transitions:

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: logger.log(Message.melted)
        case .logFrozen: logger.log(Message.frozen)
        case .logVaporized: logger.log(Message.vaporized)
        case .logCondensed: logger.log(Message.condensed)
        }
    }
}

Perform state transitions:

expect(stateMachine.state).to(equal(.solid))

// When
let transition = try stateMachine.transition(.melt)

// Then
expect(stateMachine.state).to(equal(.liquid))
expect(transition).to(equal(
    StateMachine.Transition.Valid(fromState: .solid, event: .melt, toState: .liquid, sideEffect: .logMelted))
)
expect(logger).to(log(Message.melted))

Swift Enumerations with Associated Values

Due to Swift enumerations (as opposed to sealed classes in Kotlin), any State or Event enumeration defined with associated values will require boilerplate implementation for StateMachineHashable conformance.

The easiest way to create this boilerplate is by using the Sourcery Swift code generator along with the AutoStateMachineHashable stencil template provided in this repository. Once the codegen is setup and configured, adopt AutoStateMachineHashable instead of StateMachineHashable for the State and/or Event enumerations.

Examples

Matter State Machine

Turnstile State Machine

Kotlin Download

StateMachine is available in Maven Central.

Snapshots of the development version are available in Sonatype's snapshots repository.

Maven

<dependency>
    <groupId>com.tinder.statemachine</groupId>
    <artifactId>statemachine</artifactId>
    <version>0.3.0</version>
</dependency>

Gradle

implementation 'com.tinder.statemachine:statemachine:0.3.0'

Swift Installation

Swift Package Manager

.package(url: "https://github.com/Tinder/StateMachine.git", from: "0.3.0")

Cocoapods

pod 'StateMachine', :git => 'https://github.com/Tinder/StateMachine.git'

References

A composable pattern for pure state machines with effects - Andy Matuschak

Visualization

Thanks to @nvinayshetty, you can visualize your state machines right in the IDE using the State Arts Intellij plugin.

License

Copyright (c) 2018, Match Group, LLC
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of Match Group, LLC nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MATCH GROUP, LLC BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

GitHub

https://github.com/Tinder/StateMachine
Comments
  • 1. A Visualizer for state machine

    Hey, thanks for all the great work. I really like using State Machine DSL in my daily work. I have created an IntelliJ plugin to visualize the StateMachine. While I'm waiting for the review process by IntelliJ to complete for it to publicly available, could you share your opinion/ feedback on my work here https://github.com/nvinayshetty/StateArts

    Reviewed by nvinayshetty at 2019-03-31 09:46
  • 2. cannot find gradle dependency

    which repository is in it ? i tried putting in android studio but i get gradle reporting that it cannot resolve it?

    Failed to resolve: com.tinder:state-machine:0.0.1

    im using these ones:

     jcenter()
    
            maven { url 'https://maven.fabric.io/public' }
            mavenCentral()
            google()
    
    Reviewed by j2emanue at 2018-06-18 03:08
  • 3. Add Swift implementation

    Changelog

    • Add Swift implementation

    Updated README

    Preview: https://github.com/Tinder/StateMachine/blob/a1892cca28b5d5712f984261cb67630c3a2213fc/README.md

    Reviewed by tinder-cfuller at 2020-08-11 23:51
  • 4. Multiple Sideeffects

    It would be nice if the state machine could allow for multiple side effects on an transition. For example one (always used) side effect of logging the transition and one highly specialized for the stateA ---> stateB transition (caused by eventC)

    I image the call site to look something like this:

     state<MachineState.INOPERATIVE> {
            on<Event.OnPacket> {
                if (it.fancyVariable === 123) {
                    transitionTo(MachineState.STARTING,
                            listOf(SideEffect.FrobnicateBar(it.requestTime),
                                   SideEffect.WriteStateTransition()))
                } else {
                    dontTransition()
                }
            }
        }
    

    To also allow the old style of only specifying one side effect it seems like I would need to overload StateMachine.StateDefinitionBuilder.transitionTo() and dontTransition()

    I would be happy to try this (given some pointers on where to start).

    Reviewed by geertijewski at 2020-06-23 15:23
  • 5. more open License please :)

    any chance to set this one file library to a more open license, e.g. MIT, Apache 2, etc.? it's neat but that license makes it basically unusable to almost every commercial application. i wonder if that is truly necessary for this particular library. have a nice day

    Reviewed by kibotu at 2019-08-06 07:22
  • 6. More than one SideEffect?

    Hi, so I was wondering if there's a recommended way to have more than one SideEffect for a transition? Is it simply repeating a transitionTo to the destination state but with different SideEffects?

    And yes, I realize I could just simply have unique SideEffects for each transition, but that would lead to a lot of repeated code in my case. If I'm missing anything please let me know. Thanks!

    For example (see // THIS comments):

    val stateMachine = StateMachine.create<State, Event, SideEffect> {
        initialState(State.Solid)
        state<State.Solid> {
            on<Event.OnMelted> {
                transitionTo(State.Liquid, SideEffect.LogMelted)
                transitionTo(State.Liquid, SideEffect.LogOtherLiquidInfo) // THIS
            }
        }
        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)
                transitionTo(State.Liquid, SideEffect.LogOtherLiquidInfo)  // THIS
            }
        }
        onTransition {
            val validTransition = it as? StateMachine.Transition.Valid ?: [email protected]
            when (validTransition.sideEffect) {
                SideEffect.LogMelted -> logger.log(ON_MELTED_MESSAGE)
                SideEffect.LogFrozen -> logger.log(ON_FROZEN_MESSAGE)
                SideEffect.LogVaporized -> logger.log(ON_VAPORIZED_MESSAGE)
                SideEffect.LogCondensed -> logger.log(ON_CONDENSED_MESSAGE)
                SideEffect.LogOtherLiquidInfo-> logger.log(ON_OTHER_LIQUID_INFO)  // THIS
            }
        }
    }
    
    Reviewed by franciscoaleixo at 2019-06-13 17:11
  • 7. Add parameter to enable throwing exception on invalid transition

    As proposed in https://github.com/Tinder/StateMachine/issues/9, invalid transition should cause an exception. In my opinion, current behavior (returning Invalid object) is a good solution, however users should be able to enable throwing exceptions in such cases.

    Reviewed by pchmielowski at 2019-05-22 11:04
  • 8. Throw exception with meaningful error description in case of missing state definition.

    When a state definition is missing, exception is thrown with message: Required value is null. This fix adds more meaningful error message.

    An example of missing state definition:

                private val stateMachine = StateMachine.create<String, Int, Nothing> {
                    initialState(STATE_A)
                    state(STATE_A) {
                        on(EVENT_1) {
                            transitionTo(STATE_B)
                        }
                    }
                    // Missing STATE_B definition.
                }
    

    Error is thrown here:

    stateMachine.transition(EVENT_1) // transition to `STATE_B` which is not handled.
    
    Reviewed by pchmielowski at 2019-05-22 10:41
  • 9. Add state management

    Hello guys! Finite-state machine in theory has a "final states" For example simple task "Sequence of 1 and 0 contain odd 0 and even 1" has only one final state in graph. Will be better extend this example for handling this possibility.

    Also sometimes we should reset our state machine to init state.

    What do u think about this changes or it is unnecessary?

    Reviewed by Vacxe at 2018-09-04 03:31
  • 10. Question: only states and events (no side effects)

    Hi,

    I have implemented and ad-hoc Bluetooth connection state machine based on states and events (no need for SideEffects / actions for the moment). I was thinking about switching that state machine to a version created using this library.

    Is it possible to create such state machine, or as a minimum I should have at least 1 SideEffect (a dummy one for example)?

    Thanks in advance.

    Reviewed by Dario-GoldenSpear at 2022-05-24 19:04
  • 11. Adding support for suspend functions

    Wanted to say thanks for developing this state machine library! Are there plans in the future to add support for calling suspending functions from within the handlers? Right now to workaround this we need to do something like

    state<State.MyState> {
      on<Event.MyEvent> {
        runBlocking {
          mySuspendingFunction()
        }
      }
    }
    

    Would be nice to just be able to call mySuspendingFunction() from directly within the event handler.

    Also, is the version 0.3.0 going to be published to maven? It looks like the most recent version on maven is 0.2.0 https://mvnrepository.com/artifact/com.tinder.statemachine/statemachine.

    Reviewed by bruuuuuuuce at 2022-05-06 20:26
  • 12. Add onEnter and onExit events to states

    Those event are present in an android version of the state machine and are missed in iOS version, so it is not possible to use the same logic between platforms.

    Reviewed by Gray-Wind at 2022-02-26 08:21
  • 13. OnEnter not called for initialState

    Thank you for your amazing library I have a problem with onEnter at initialState.

    It's my code :

       val machine = StateMachine.create<State, Event, SideEffect> {
       initialState(State.INIT)
       
       state<State.INIT> {
          onEnter {
             println("onEnter INIT")
          }
          
          on<Event.E1> {
             println("onE1 INIT")
             transitionTo(State.INITIALIZED)
          }
       }
       
       state<State.INITIALIZED> {
          onEnter {
             println("enter initialized")
          }
          
          on<Event.E1> {
             transitionTo(State.FINAL)
          }
          
          onExit {
             println("exit initialized")
          }
       }
       
       state<State.FINAL> {
          onEnter {
             println("onFinal")
          }
       }
       
       
    }
    
    
    machine.transition(Event.E1)
    machine.transition(Event.E1)
    
    sealed class State {
       object INIT : State()
       object INITIALIZED : State()
       object FINAL : State()
    }
    
    sealed class Event {
       object E1 : Event()
    }
    
    sealed class SideEffect {
       
    }
    

    Result:

    onE1 INIT enter initialized exit initialized onFinal

    Reviewed by dariush-fathie at 2021-04-11 08:11

Related

A fresh and modern Google Contacts manager that integrates with GitHub and Twitter.

Flokk A fresh and modern Google Contacts manager that integrates with GitHub and Twitter. Demo Builds Web: https://flokk.app Linux: https://snapcraft.

Jun 25, 2022
✨A clean and lightweight loading/toast widget for Flutter, easy to use without context, support iOS、Android and Web
✨A clean and lightweight loading/toast widget for Flutter, easy to use without context, support iOS、Android and Web

Flutter EasyLoading English | 简体中文 Live Preview ?? https://nslog11.github.io/flutter_easyloading Installing Add this to your package's pubspec.yaml fi

Jun 22, 2022
This repository demonstrates use of various widgets in flutter and tricks to create beautiful UI elements in flutter for Android and IOS
This repository demonstrates use of various widgets in flutter and tricks to create beautiful UI elements in flutter for Android and IOS

AwesomeFlutterUI The purpose of this repository is to demonstrate the use of different widgets and tricks in flutter and how to use them in your proje

May 20, 2022
A collection of Screens and attractive UIs built with Flutter ready to be used in your applications. No external libraries are used. Just download, add to your project and use.
A collection of Screens and attractive UIs built with Flutter ready to be used in your applications. No external libraries are used. Just download, add to your project and use.

Flutter Screens A collection of Login Screens, Buttons, Loaders and Widgets with attractive UIs, built with Flutter, ready to be used in your applicat

Jul 1, 2022
A flutter package which contains a collection of some cool and beautiful effects; support android and ios
A flutter package which contains a collection of some cool and beautiful effects; support android and ios

flutter effects A flutter package which contains a collection of some cool and beautiful effects; support android and ios . Screenshot type support ch

Jun 21, 2022
Flexible and easy to use page transitions.
Flexible and easy to use page transitions.

flutter_villains What are heroes without villains? (Profile image from: https://unsplash.com/photos/pAs4IM6OGWI) Check out the article. What are villa

May 29, 2022
🔔 A flutter package to create cool and beautiful text animations. [Flutter Favorite Package]
🔔  A flutter package to create cool and beautiful text animations. [Flutter Favorite Package]

Animated Text Kit A flutter package which contains a collection of some cool and awesome text animations. Recommended package for text animations in C

Jun 29, 2022
Easily add staggered animations to your ListView, GridView, Column and Row children.
Easily add staggered animations to your ListView, GridView, Column and Row children.

Flutter Staggered Animations Easily add staggered animations to your ListView, GridView, Column and Row children as shown in Material Design guideline

Jun 23, 2022
Fun canvas animations in Flutter based on time and math functions.
Fun canvas animations in Flutter based on time and math functions.

funvas Flutter package that allows creating canvas animations based on time and math (mostly trigonometric) functions. The name "funvas" is based on F

Jun 15, 2022
A widget for stacking cards, which users can swipe horizontally and vertically with beautiful animations.
A widget for stacking cards, which users can swipe horizontally and vertically with beautiful animations.

A widget for stacking cards, which users can swipe horizontally and vertically with beautiful animations.

Jun 18, 2022
A catalog of beautiful, reusable and elegant animations
A catalog of beautiful, reusable and elegant animations

Animations Catalog The goal of this project is to catalog and build multiple animation patterns with flutter. Budget App Animation Harley Swipe To Enl

Sep 6, 2021
Beautiful Animated ListView and GridView

staggered_animated_listview Beautiful Animated ListView and GridView Online Preview Getting Started This project is a starting point for a Flutter app

Dec 11, 2021
A package to create nice and smooth animations for flutter
A package to create nice and smooth animations for flutter

animation_director A package to create nice and smooth animations for flutter Introduction A simple package to build beautiful and smooth animations f

Dec 5, 2021
An awesome list that curates the best Flutter libraries, tools, tutorials, articles and more.
An awesome list that curates the best Flutter libraries, tools, tutorials, articles and more.

Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. If you appr

Jun 27, 2022
A Flutter package for a quick and handy giffy dialog.
A Flutter package for a quick and handy giffy dialog.

?? Giffy Dialogs A beautiful and custom alert dialog for flutter highly inspired from FancyAlertDialog-Android. The source code is 100% Dart, and ever

Jun 23, 2022
a widget provided to the flutter scroll component drop-down refresh and pull up load.
a widget provided to the flutter scroll component drop-down refresh and pull up load.

flutter_pulltorefresh Intro a widget provided to the flutter scroll component drop-down refresh and pull up load.support android and ios. If you are C

Jun 21, 2022
Help you to build pull-down refresh and pull-up loading in the simplest way.
Help you to build pull-down refresh and pull-up loading in the simplest way.

frefresh Help you to build pull-down refresh and pull-up loading in the simplest way. Although unprecedented simplicity, but the effect is amazing. It

Jun 15, 2022
Basic login and signup screen designed in flutter
Basic login and signup screen designed in flutter

flutter_login_signup Simple basic authentication app designed in flutter. App design is based on Login/Register Design designed by Frank Arinze Downlo

Jun 21, 2022
Simple and configurable app introduction slider for Flutter
Simple and configurable app introduction slider for Flutter

FLUTTER INTRO SLIDER Flutter Intro Slider is a flutter plugin that helps you make a cool intro for your app. Create intro has never been easier and fa

Jun 17, 2022