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

Overload for collectWhileInState with Builder

sockeqwe opened this issue · comments

Currently we support collectWhileInState with flowBuilder as shown bellow:

 fun <T> collectWhileInState(
        flowBuilder: (Flow<InputState>) -> Flow<T>,  
        handler: InStateObserverHandler<T, InputState, S>
 )

I would like to add an overload with

 fun <T> collectWhileInState(
        flowBuilder: (InputState) -> Flow<T>, // just InputState , not Flow<InputState> as above
        handler: InStateObserverHandler<T, InputState, S>
 )

because I personally have more need for this variation of flowBuilder ( (InputState) -> Flow<T>) compare to the current one (Flow<InputState>) -> Flow<T>.

Obviously, that causes a clash on the JVM because both methods have same signature.

Thus we would need to rename one.

I would propose the following:

  • My assumption is that most people have use case for the version with (InputState) -> Flow<T> . Thus I would keep that one under the collectWhileInState name.
 fun <T> collectWhileInState(
        flowBuilder: (InputState) -> Flow<T>, // just InputState , not Flow<InputState> as above
        handler: InStateObserverHandler<T, InputState, S>
 )
  • I would rename the one with Flow<InputState>) -> Flow<T> to collectWhileInStateWithGenericBuilder():
fun <T> collectWhileInStateWithGenericBuilder(
       flowBuilder: (Flow<InputState>) -> Flow<T>,  
       handler: InStateObserverHandler<T, InputState, S>
)

Any thoughts?

cc @gabrielittner

Couldn't this be a foot gun? You create a Flow based on a value in the state, but if it's in the state it means that the value can change through mutations. Even if you know that the value does not change without you leaving the state first I think it would still be safer to do { stateFlow -> stateFlow.flatMapLatest { inputState -> myFlow(inputState) } } opposed to just { inputState -> myFlow(inputState) }. Or would the flowBuilder in your proposal be called whenever InputState changes and we do the flatMapLatest internally?

Yes, but that is in general also an issue for on<Action> and other friends because the passed (as parameter) state can already be mutated in the meantime. Example:

data class FooState(val value : Int)

inState<FooState> {
   onAction<FooAction> { stateSnapshot, action ->
      delay(2000)
       ... 
      // Some properties like stateSnapshot.value could be outdated because of mutations happened in parallel

   loadItem(stateSnapshot.value)

   ...
}

In that case we need to rely on using MutateState properly or set up inState(condition) properly to cancel if necessary for onAction to give some guarantees

After I've added the indentity block (#480) I will also tackle this one. The motivation for flowBuilder: (Flow<InputState>) -> Flow<T> was to enable collecting a flow that depends on a value of the state by chaining it through a flatMapLatest. The identity block together with this proposal achieves the same:

inState<Foo> {
    untilIdentityChanged({ it.bar }) {
        collectWhileInState({ createFlow(it.bar) }) { ... }
    }
}

This approach fits better to the descriptive nature of building a state machine and is less dangerous than the approach of transforming a flow. While refactoring some things in preparation of these changes I've also noticed that the (Flow<InputState>) -> Flow<T> introduces additional complexity to the library. The current builder would be deprecated.

One question would be the name since the initial renaming proposal won't work since we've reached 1.x.