-
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, Mutation, State> {
fun dispatch(action: Action)
val currentState: State
val state: Flow<State>
}a Controller is built via extension functions on a CoroutineScope
// action triggered by view
sealed class Action {
data class SetValue(val value: Int) : Action()
}
// mutation that is used to alter the state
sealed class Mutation {
data class SetMutatedValue(val mutatedValue: Int) : Mutation()
}
// immutable state
data class State(
val value: Int
)
// Controller is created in a CoroutineScope
val valueController = someCoroutineScope.createController<Action, Mutation, State>(
// we start with the initial state
initialState = State(value = 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(value = 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 state Flow itself is started (meaning accepting actions, mutating and reducing), depending on the CoroutineStart parameter available in the builders.kt. the default selection is CoroutineStart.LAZY, which starts the state once Controller.state, Controller.currentState or Controller.dispatch(...) are accessed.
when any Throwable's are thrown inside Controller.mutator or Controller.reducer they re-thrown as wrapped RuntimeException's.