Skip to content

Custom condition inside inState

We already covered inState<State> that builds upon the recommended best practice that every State of your state machine is expressed us it’s own type in Kotlin.

Sometimes, however, you need a bit more flexibility than just relaying on types to model state. For that use case you can add condition( isConditionMet: (State) -> Boolean) blocks inside your inState<State> blocks.

Example: One could have also modeled the state for our ItemListStateMachine as the following:

// TO MODEL YOUR STATE LIKE THIS IS NOT BEST PRACTICE!
// In a real world example we recommend using sealed class instead.
data class ListState(
    val loading: Boolean, // true means loading, false means not loading
    val items: List<Items>, // empty list if no items loaded yet
    val errorMessage: String?, // if not null we are in error state
    val errorCountDown: Int? // the seconds for the error countdown
)

AGAIN, the example shown above is not the recommended way. We strongly recommend to use sealed classes instead to model state as shown at the beginning of this document.

We just do this for demo purpose to demonstrate a way how to customize inState. Given the state from above, what we can do now with our DSL is the following:

class ItemListStateMachine(
    private val httpClient: HttpClient
) : FlowReduxStateMachine<ListState, Action>(
    initialState = State(
        loading = true,
        items = emptyList(),
        error = null,
        errorCountDown = null
    )
) {

    init {
        spec {
            inState<ListState> {
                condition({ state -> state.loading == true }) {
                    onEnter { state: State<ListState> ->
                        // we entered the Loading, so let's do the http request
                        try {
                            val items = httpClient.loadItems()
                            state.mutate {
                                this.copy(loading = false, items = items, error = null, errorCountdown = null)
                            }
                        } catch (t: Throwable) {
                            state.mutate {
                                this.copy(
                                    loading = false, 
                                    items = emptyList(), 
                                    error = "A network error occurred", 
                                    errorCountdown = 3
                                )
                            }
                        }
                    }
                }

                condition({ state -> state.error != null }) {
                    on<RetryLoadingAction> { action : RetryLoadingAction, state : State<ListState> ->
                        state.mutate {
                            this.copy(loading = true, items = emptyList(), error = null, errorCountdown = null)
                        }
                    }

                    val timer : Flow<Int> = timerThatEmitsEverySecond()
                    collectWhileInState(timer) { value : Int, state : State<ListState> ->
                        state.mutate {
                            if (errorCountdown!! > 0)
                                //  decrease the countdown by 1 second
                                this.copy(errorCountdown = this.errorCountdown!! - 1)
                            else
                                // transition to the Loading
                                this.copy(
                                    loading = true,
                                    items = emptyList(),
                                    error = null,
                                    errorCountdown = null
                                )
                        }
                    }
                }
            }
        }
    }
}

condition takes a lambda as parameter with the following signature: (State) -> Boolean. If that lambda returns true it means the condition is met, otherwise not (returning false). You can use onEnter, on<Action> and collectWhileInState the exact way as you already know, just wrapped around a condition block.