-
Notifications
You must be signed in to change notification settings - Fork 9
controller
A Controller is an ui-independent class that controls the state of a view. The role of a Controller is to separate business-logic from view-logic. A Controller has no dependency to the view, so it can easily be unit tested.

interface Controller<Action, State> {
fun dispatch(action: Action)
val state: StateFlow<State>
}a regular Controller is built via extension function on a CoroutineScope
// action triggered by view
sealed interface Action {
data class SetValue(val value: Int) : Action
}
// mutation that is used to alter the state
private sealed interface Mutation {
data class SetMutatedValue(val mutatedValue: Int) : Mutation
}
// immutable state
data class State(
val counterValue: Int
)
// Controller is created in a CoroutineScope
val valueController = someCoroutineScope.createController<Action, Mutation, State>(
// we start with the initial state
initialState = State(counterValue = 0),
// every action is transformed into [0..n] mutations
mutator = { action ->
when (action) {
is Action.SetValue -> flow {
delay(5000) // some asynchronous action
val mutatedValue = action.value + 1
emit(Mutation.SetMutatedValue(mutatedValue))
}
}
},
// every mutation is used to reduce the previous state to a
// new state that is then published to the view
reducer = { mutation, previousState ->
when (mutation) {
is Mutation.SetMutatedValue -> previousState.copy(counterValue = mutation.mutatedValue)
}
}
)since initialState, mutator, reducer and the different transformation functions can be provided via the builder of the Controller, a high degree of composition is possible.
the Controller lives as long as the CoroutineScope is active that it is built in.
the internal state machine is started (meaning accepting actions, mutating and reducing), depending on the ControllerStart parameter available in the builders. the default selection is ControllerStart.Lazy, which starts the state machine once Controller.state or Controller.dispatch(...) are accessed.
when any Throwable's are thrown inside Controller.mutator or Controller.reducer they re-thrown as wrapped RuntimeException's.