freeletics / FlowRedux

Kotlin Multiplatform Statemachine library with nice DSL based on Flow from Kotlin Coroutine's.

Home Page:https://freeletics.github.io/FlowRedux/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Turn handler typealiases into functional interfaces

gabrielittner opened this issue · comments

When you build a state machine we ideally want something like this

class MyStateMachine(...) : FlowReduxStateMachine(...) {
  init {
    spec {
       inState<MyState> {
         on(::onAction1TransisitionToA)
         on(::onAction2DoSomethingElse)
       }
    }
  }
}

suspend fun onAction1TransisitionToA(action: Action1, stateSnapshot: MyState): ChangeState<MyState> {
  // do stuff
}

suspend fun onAction2DoSomethingElse(action: Action2, stateSnapshot: MyState): ChangeState<MyState> {
  // do stuff
}

Having the handlers as standalone methods lets you easily test them without having to build the full state machine. Method references in the DSL help a lot with keeping the definition itself readable when the state machine gets bigger.

The issue arises when the handler needs some dependency. You either need to

  1. move the function into the state machine which hurts testability
  2. add the parameter to the function, which would mean to stop using method references
  3. put the function into it's own class that injects the required parameters

The latter would look like this

class MyStateMachine(
  action1TransisitionToA: Action1TransisitionToA,
  action2DoSomethingElse: Action2DoSomethingElse,
   ...
) : FlowReduxStateMachine(...) {
  init {
    spec {
       inState<MyState> {
         on(action1TransisitionToA::handle)
         on(action2DoSomethingElse::handle)
       }
    }
  }
}

class Action1TransisitionToA(...) {
  suspend fun handle(action: Action1, stateSnapshot: MyState): ChangeState<MyState> {
    // do stuff
  }
}

class Action2DoSomethingElse(...) {
  suspend fun handle(action: Action2, stateSnapshot: MyState): ChangeState<MyState> {
    // do stuff
  }
}

This works with everything that we have however we could improve the experience by turning

typealias OnActionHandler<InputState, S, A> = suspend (action: A, state: InputState) -> ChangeState<S>

into

fun interface OnActionHandler<InputState, S, A> {
  suspend fun handle(action: A, state: InputState): ChangeState<S>
}

That way those classes can implement the interface which makes implementing it easier and also prevents any unused_parameter warning on the function. Any existing usage and usecase would still work like before because fun interface support SAM conversion.

Good idea! That way we can have the best (compromise) of all worlds!

Btw. I will be on vacation next 2 weeks