A Kotlin and Swift DSL for finite state machine

Overview

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.
Comments
  • A Visualizer for state machine

    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

    opened by nvinayshetty 8
  • Useful as a UI Architecture?

    Useful as a UI Architecture?

    ~~Is it possible to attach data to states and events using the DSL? I've been avoiding using Mobius or RxRedux for implementing state machines as part of UI architecture and just writing my own. I'd like to use this DSL to describe my state machines for the sake of formalization and reusability, but I can't see a way to make my states and events meaningful in terms of attaching data to them. Should I just write my own DSL that suits my needs? :[~~

    ~~Edit: For clarification, my pain point is when I'm declaring states to transition to. The transitionTo builder method wants a state instance, but the state instance, in my case, can only be constructed once an event with a payload is received and parsed. Is it possible to access the event that caused the transition when declaring the transitionTo, perhaps in the scope of on<SomeEvent> { ... }?~~

    Turns out you can optionally specify the presence of an event parameter for the lambda of on<SomeEvent> { ... }. Sneaky.

    Another question, then - are SideEffects triggering new Events supported with this DSL? I don't see an obvious way to dispatch new events in onTransition { ... } since the state machine isn't created yet.

    opened by carterhudson 3
  • cannot find gradle dependency

    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()
    
    opened by j2emanue 2
  • Apply DSL Scope Control

    Apply DSL Scope Control

    add @StateMachineDsl, so that onEnter{} could not be called inside on {}

    but it modified the api, so probably it can not be merged, just an idea to share

    opened by Ray-Jony 1
  • Add Swift implementation

    Add Swift implementation

    Changelog

    • Add Swift implementation

    Updated README

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

    opened by tinder-cfuller 1
  • Multiple Sideeffects

    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).

    opened by geertijewski 1
  • more open License please :)

    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

    opened by kibotu 1
  • More than one SideEffect?

    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 ?: 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)
                SideEffect.LogOtherLiquidInfo-> logger.log(ON_OTHER_LIQUID_INFO)  // THIS
            }
        }
    }
    
    opened by franciscoaleixo 1
  • Add parameter to enable throwing exception on invalid transition

    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.

    opened by pchmielowski 0
  • Throw exception with meaningful error description in case of missing state definition.

    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.
    
    opened by pchmielowski 0
  • Add state management

    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?

    opened by Vacxe 0
  • Question: only states and events (no side effects)

    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.

    opened by Dario-GoldenSpear 2
  • Adding support for suspend functions

    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.

    opened by bruuuuuuuce 0
  • Add onEnter and onExit events to states

    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.

    opened by Gray-Wind 1
  • OnEnter not called for initialState

    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

    opened by dariush-fathie 1
  • How do you save state?

    How do you save state?

    I can't see any ways to store and restore states of state machine. Especially I'm interested in restoring state with specific side effects. How do you usually do it?

    opened by okatopilskiiko 0
Releases(0.3.0)
Owner
Tinder
Tinder Open Source Software
Tinder
Progress State Button - A customizable progress button for Flutter

Progress State Button - A customizable progress button for Flutter

Selim 108 Dec 12, 2022
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.

gskinner team 1.3k Jan 3, 2023
✨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

nslog11 1k Jan 9, 2023
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

Subir Chakraborty 132 Nov 13, 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.

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

Samarth Agarwal 5k Dec 31, 2022
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

大海豚 462 Jan 3, 2023
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

Norbert Kozsir 356 Dec 12, 2022
🔔 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

Ayush Agarwal 1.4k Jan 6, 2023
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

null 1.2k Jan 6, 2023
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

null 472 Jan 9, 2023
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.

HeavenOSK 97 Jan 6, 2023
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

null 3 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

Elyas Sekhavati Nia 1 Dec 11, 2021
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

null 10 Nov 28, 2022
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

Robert Felker 44.6k Dec 30, 2022
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

Sahil Kumar 626 Dec 29, 2022
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

Jpeng 2.5k Jan 5, 2023
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

Fliggy Mobile 427 Nov 26, 2022
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

Sonu Sharma 551 Jan 6, 2023