-
Notifications
You must be signed in to change notification settings - Fork 8
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.