GithubHelp home page GithubHelp logo

statemachine's People

Contributors

aaronweihe avatar adrielcafe avatar tinder-ahe avatar tinder-cfuller avatar tinder-zlai avatar zhxnlai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

statemachine's Issues

Can't test transitions

Hi,

As in your readme file, I would like to test the transitions:

assertThat(transition).isEqualTo(
    StateMachine.Transition.Valid(Solid, OnMelted, Liquid, LogMelted)
)

But, the "Valid" data class has an internal constructor so I can't access it in my tests.

What should I do ? Thanks.

Add CircleCI config to the repo

There should be a CircleCI configuration file pushed to the repo. Because there isn't one, the config isn't copied to fork repos and any cross-repo PR will fail unless the contributor sets up his or her own CI config, which is probably not the best idea.

Access all possible events from a certain state?

If this is confusing, please let me know.

Given the test code:

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

I want to know how I could, for example, do
stateMachine.validEventsFor(State.Liquid) // -> list(Event.OnFroze, event.OnVaporized)

Is there a way to do this, even with reflection?

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

Is it possible to start the machine in a specific state?

I have a use case where the state machine is governing a long running process that spans over multiple device restarts (android). Is there a way I can start the machine in a specific state (different from default initial state)?

Related to #7

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.

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

Is there a wildcard state syntax to signify all states?

Is there a way to express all states as the start of a transition? In other words, if there is a transition that causes all states to go to one single state, how can that be expressed in the transition code? For example, consider a 'reset' transition that makes the state machine go to a Start state regardless of the current state I can write a transition rule from each state to Start using the event reset, but it would be more readable to have something like

state<State.*> {
            on<Event.reset> {
                transitionTo(State.Start)
            }
}

Is there a syntax for something like this?
Thanks!
Mark

Is something wrong with my state machine?

Hi, amazing library. I'm trying to implement it in my own project.

These are my states

sealed class State {
    object Loading : State()
    class Success(val response: SomeDataClass) : State()
    class Failure(val exception: Exception) : State()
}

These are the events:

sealed class Event {
    class OnApiSuccess(val response: SomeDataClass) : Event()
    class OnApiFailure(val exception: Exception) : Event()
}

Finally this is the side effect:

sealed class SideEffect {
    object AfterApiSuccess : SideEffect()
    object AfterApiFailure : SideEffect()
}

This is the implementation of state machine:

val stateMachine = StateMachine.create<State, Event, SideEffect> {
    initialState(State.Loading)
    state<State.Loading> {
        on<Event.OnApiSuccess> {
            transitionTo(State.Success(it.response), SideEffect.AfterApiSuccess)
        }
        on<Event.OnApiFailure> {
            transitionTo(State.Failure(it.exception), SideEffect.AfterApiFailure)
        }
    }
    state<State.Success> { }
    state<State.Failure> { }

    onTransition {
        if (it is StateMachine.Transition.Valid) {
            when (it.sideEffect) {
                SideEffect.AfterApiSuccess ->
                    Log.e("StateMachine", "Current State is : ${it.toState.javaClass.simpleName}")
                SideEffect.AfterApiFailure ->
                    Log.e("StateMachine", "Current State is : ${it.toState.javaClass.simpleName}")
            }
        } else if (it is StateMachine.Transition.Invalid) {
            Log.e("StateMachine", "Something went wrong")
        }
    }
}

I have a MutableLiveData in my viewmodel which observes State
val stateObserver: MutableLiveData<State> = MutableLiveData()
to which I'm posting value as
stateObserver.postValue(stateMachine.transition(Event.OnApiSuccess(response)).fromState)
or stateObserver.postValue(stateMachine.transition(Event.OnApiFailure(exception)).fromState)

And my implementation of LiveData observer is as below:

someViewModel.stateObserver.observe(this, Observer { state ->
            when (state) {
                is State.Loading -> {
                    progress.visibility = View.VISIBLE
                    list.visibility = View.GONE
                    error.visibility = View.GONE
                }
                is State.Success -> {
                    progress.visibility = View.GONE
                    list.visibility = View.VISIBLE
                    error.visibility = View.GONE
                    Log.e("TAG", "Response is :${state.response.results}")
                }
                is State.Failure -> {
                    progress.visibility = View.GONE
                    list.visibility = View.GONE
                    error.visibility = View.VISIBLE
                    Log.e("TAG", "Error is :${state.exception.message}")
                }
            }
        })

I don't what is I'm doing wrong but my app is getting stuck on loading. Help is much appreciated. Thanks.

State machine design: transitioning before effect?

Hi, I wanna bounce of you a design question, since im trying to rewrite "implicit" state machine to explicit one

Usually the code looks like this

fun foo() {
   if (currentState == Bar) {
    doSomething()
    currentState = Quax
   }
}

What I usually see is people having transition function, which according to input action changes state, but however it would do so before doSomething is ran, to me which is a problem
since currentState will be observable, and subscribers expect doSomething to have ran if state is Quax

Do I need some PRE + POST_state pairs or something? Seems weird for synchronous code

What would be your solution?

onEnter and side effects

First of all, thanks for this awesome library.
And I have a little question about underlying theory.
Is onEnter callback applicable for side effects (in terms of finite state machine theory)?
Or should I create a model for every side effect and perform them exclusively in onTransition callback?
Thanks in advance.

Can you publish to the `toState` property of `Transition` class?

Because I want to determine the action according to the toState state.
If you can I can create an extension function then I can write to flow.

ex)

If toState property is publish and you define below extension

fun State.navigate() {
  when(this) {
     Solid -> foo
  }
}

we can write the following:

stateMachine.transition(<SomeEvent>).toState.navigate()

Now we can only write the following:

stateMachine.run {
    transition(it)
    state.navigate()
}

Thank you.

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?

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.

Handling asynchronous events

Do you support asynchronous event handling ?

I mean, does the transition wait for the side effect to complete to change state ?

I would like to do a network call on Event before applying a transition. Since I can't do it inside the on<Event.Foo> {} what if I do that as a side effect, would the side effect 'block' the state till the asynchronous call is done ?

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

State of affairs inquiry

Hi.

First of all, thank you for all of your hard work and effort in putting this library together.
A few things:

  1. Is this project alive and actively maintained? are there any additional releases planned?
  2. Aritfact publishing is a bit messy at the moment, Gradle build does not properly set up version/group in accordance to what's published (up to a point local artifact publishing isn't supported OOTB). And of course, no-snapshots available :-(
  3. Some code design decisions make it hard to share/reuse code in the state-machine declarations. I can give a concrete example, but I'd be more than happy to try and fix it myself.
  4. Would you be willing to accep pull requests?

@zhxnlai 10x in advance

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

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

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.

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

Throw exception on the wrong event

What if state machine will throw an exception when we try to do transition to some state by undeclared event (sorry for my english).
For example:
declaring state machine behaviour

initialState(State.SomeState)
state<State.SomeState> {
            on<Event.SomeEvent> {
                transitionTo(State.AnotherState, SideEffect.SomeSideEffect)
            }
}

then we trying to do undeclared transition (from state "SomeState")
stateMachine.transition(Event.SomeOtherEvent) //<- there is should be thrown an exception

State machine's dontTransition() method

Hi, I really appreciate your implementation of state machine.

I have one question though: you have this method called dontTransition() which makes the state machine reenter the same state. What would be the way to workaround this and not reenter the same state? Is there something like ignoreEvent() method?

Thanks.

logo design

Hello! I want to contribute on your project with making logo design. What do you think about it?

Best Regards,
Arslan Şahin
Graphics Designer

Directly set the state for testing purposes

In my usage of this library, it is very inconvenient to set up unit tests where I want the state machine to be in a certain state. What I've been doing so far is creating utility functions that perform all the transitions necessary from the initial state to the desired state.

Is there a way to do what I am describing?

java.lang.IllegalStateException if StateMachine doesn't contain description of toState

I have final state (ERROR) which hasn't any transitions or specific actions. Should I describe it anyway in empty state<TaskStateType.ERROR> branch?
Now I miss it so I receive

java.lang.IllegalStateException: Missing definition for state Error!

because Error is absent in graph.stateDefinitions and com.tinder.StateMachine#notifyOnEnter method fails with such exception.

My state machine definition:

        StateMachine.create<TaskStateType, StateEventType, TaskStateHandler> {
            initialState(TaskStateType.New)
            state<TaskStateType.New> {
                on<StateEventType.OnWait> {
                    dontTransition()
                }
                on<StateEventType.OnError> {
                    transitionTo(TaskStateType.Error, handlerFactory.findHandlerByEvent(EventType.ERROR))
                }
            }
            
           // TaskStateType.Error is missed here
           
            onTransition {
                val transition = it as? StateMachine.Transition.Valid
                transition.sideEffect?.handle(context)
            }
        }

Need I change it to:

        StateMachine.create<TaskStateType, StateEventType, TaskStateHandler> {
            initialState(TaskStateType.New)
            state<TaskStateType.New> {
                on<StateEventType.OnWait> {
                    dontTransition()
                }
                on<StateEventType.OnError> {
                    transitionTo(TaskStateType.Error, handlerFactory.findHandlerByEvent(EventType.ERROR))
                }
            }
            state<TaskStateType.Error> {
              // do nothing
            }
            onTransition {
                val transition = it as? StateMachine.Transition.Valid
                transition.sideEffect?.handle(context)
            }
        }

It looks pretty ugly. May be I define something wrong so I need your help :-)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.