-
Notifications
You must be signed in to change notification settings - Fork 3
StateReserve
The StateReserve
holds the State
.
class StateReserve<S : State>(
val config: StateReserveConfig,
private val initialState: InitialState<S>,
private val reduce: Reduce<S>,
middlewares: List<Middleware<S>>?,
) {..}
Internally, the StateReserve
contains a StateMachine
which holds the current State
. This StateMachine
is based on the actor model which in turn is based on the concept of
"Do not communicate by sharing memory; instead, share memory by communicating."
The StateReserve
uses Channel
to communicate with the StateMachine
. By doing so, the State
is confined to a particular thread
, which satisfies the 1st rule "Mutable state == 1 thread" and 2nd rule "Immutable state == many threads" in Kotlin Native Concurrency.
The StateReserve
provides the State
changes as a Flow<State>
.
It has five functions:
-
dispatch(action: Action)
function, through which we sendAction
. -
getState()
function provides access toState
synchronously. -
awaitState()
suspend function provides theState
after processing all the pending Actions through areducer
. -
restoreState()
To restore the initial state, after recovering from a failure or process death. The initial state can be restored only once. -
terminate()
cancels the scope of theStateReserve
.
The following flows are provided by StateReserve
-
actions: Flow<Action>
- Broadcasts theAction
as soon as they reach thedispatch
function, before theAction
going throughMiddleware
andreduce
. -
actionStates: Flow<ActionState.Always<Action, S>
- Broadcasts theAction
andState
after it passed throughMiddleware
andreduce
function. It will be broadcasted even if theAction
doesn't change theState
. -
transitions: Flow<Any>
- This flow can be collected by using the provided extension functions to listen for state transitions whenenhancedStateMachine
config is enabled.
class StateReserveConfig(
val scope: CoroutineScope,
val debugMode: Boolean,
val ignoreDuplicateState: Boolean = true,
val enhancedStateMachine: Boolean = false,
val assertStateValues: Boolean = debugMode,
val checkMutableState: Boolean = debugMode,
)
-
scope
: This is the scope for theStateReserve
and itsSideEffect
(s). We recommend to provide aCoroutineScope
withDispatchers.Default + SupervisorJob() + CoroutineExceptionHandler
. -
debugMode
: Iftrue
, checks if thereducer
is pure. -
ignoreDuplicateState
: Set this tofalse
only when using Flywheel as a direct dependency in non-JVM platforms where theequals()
andhashCode()
may not work withKotlin
. This issue happens when definingState
inObjective-C
. -
enhancedStateMachine
: To useStateReserve
as a properStateMachine
supporting listeners for state transitions. -
assertStateValues
: Just in case if you want to do the reducer check even when debug mode isfalse
. -
checkMutableState
: Checks if theState
is mutated.
Please note: assertStateValues
and checkMutableState
doesn't any check in non-JVM platforms.
Unlike Redux
, where the entire app has one store, the StateReserve
can be scoped to the entire application, to a screen, to a particular feature or even we can define one app-level StateReserve
and a StateReserve
for each screen.
In Android
and Apple
platforms, we recommended having a StateReserve
for each screen. Optionally, you can also have an app-level StateReserve
for uses cases like managing a user's authentication state, configurations, etc. There is no restriction on how to scope the StateReserve
. Make sure to call terminate()
when the StateReserve
is not required anymore.
Flywheel helps you to build a proper state machine to an extent. To use Flywheel's StateReserve
as a state machine, set enhancedStateMachine = true
in the StateReserveConfig
.
One of the important rules of a state machine is that it should not allow invalid state transition. To explain this let's see an example:
val reducer =
reducerForAction<MaterialAction, MaterialState> { action, state ->
when (action) {
MaterialAction.OnMelted -> MaterialState.Liquid
MaterialAction.OnFrozen -> MaterialState.Solid
MaterialAction.OnVaporized -> MaterialState.Gas
MaterialAction.OnCondensed -> MaterialState.Liquid
}
}
In the above reducer
we change the State
, i.e transition to another State
based on the received Action
alone. We are not taking the current State
into account while transitioning. Here we are not defining any invalid transitions. For example, a solid cannot transition to gas without changing to a liquid first. Assuming the current state is solid if we send MaterialAction.OnVaporized
action, it will transition to gas. But it's incorrect. It should not be allowed.
To restrict such invalid transitions, we have to take the current State
into account.
val reducer =
reducerForAction<MaterialAction, MaterialState> { action, state ->
when (state) {
MaterialState.Solid -> when (action) {
MaterialAction.OnMelted -> MaterialState.Liquid
else -> reduceError()
}
MaterialState.Liquid -> when (action) {
MaterialAction.OnFrozen -> MaterialState.Solid
MaterialAction.OnVaporized -> MaterialState.Gas
else -> reduceError()
}
MaterialState.Gas -> when (action) {
MaterialAction.OnCondensed -> MaterialState.Liquid
else -> reduceError()
}
}
}
In the updated reducer
, we first check the current State
and then the Action
. if no match is found we simply throw an error. So if you send an action MaterialAction.OnVaporized
it will transition only when the current State
is liquid. This way, we can enforce invalid state transition which satisfies one of the state machine behaviour. reduceError()
function is provided for this use-case to report invalid transition.
Flywheel supports listening for state transitions by collecting the transitions: Flow<Any>
in StateReserve
along with the provided extension functions.
-
specificStates<S>()
- For collecting only specific parts of aState
. This is useful when you need to collect a subset of theState
for a view component.
data class(val items: List<Any>, val selectedItems: List<Any>)
states.specificStates { it.selectedItems }.collect{..}
-
validTransitions<From, To>()
- For collecting only when valid transitions happen. Example:
transitions.validTransitions<MaterialState.Solid, MaterialState.Liquid>()
-
inValidTransition<State>()
- For collecting when a invalid transition was attempted for aAction
.
transitions.inValidTransition<MaterialState>().collect{..}
-
onEnter<State>()
- For collecting when a specificState
instance is entered.
transitions.onEnter<MaterialState.Liquid>().collect{..}
-
onExit<State>()
- For collecting when a specificState
instance is exited.
transitions.onExit<MaterialState.Liquid>().collect{..}
-
specificActions<Action>()
- For collectingAction
of specific instance types. For example, you might want to only collectAction
of typeNavigateAction
to deal with navigation-related stuff.
Please note Flywheel's StateReserve
does not adhere to the W3C SCXML specification.