tinder / statemachine Goto Github PK
View Code? Open in Web Editor NEWA Kotlin and Swift DSL for finite state machine
License: Other
A Kotlin and Swift DSL for finite state machine
License: Other
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.
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.
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?
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
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
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.
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 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
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.
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?
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.
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.
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?
Although the issue is not related to the project, looking forward to your reply
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.
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 ?
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()
Some of the state will need a timeout to go to idle or something.
How do you do that?
Can somewhere give an example?
Hi.
First of all, thank you for all of your hard work and effort in putting this library together.
A few things:
@zhxnlai 10x in advance
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
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
Hi,
I have implemented and ad-hoc Bluetooth connection state machine based on states and events (no need for SideEffect
s / 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.
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).
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
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.
Hello! I want to contribute on your project with making logo design. What do you think about it?
Best Regards,
Arslan Şahin
Graphics Designer
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?
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 :-)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.