-
Notifications
You must be signed in to change notification settings - Fork 3
Middleware
typealias Middleware<S> = (Dispatch, GetState<S>) -> (Dispatch) -> Dispatch
A middleware is a higher-order function that composes a dispatch function to return a new dispatch function. In simple words, Middleware
lets you react to Action
that yet to reach the actual dispatch
function of a StateReserve
.
The key feature of middleware is that it is composable. Multiple middlewares can be combined together, where each middleware requires no knowledge of what comes before or after it in the chain. StateReserve
accepts a list of middlewares in its constructor. Please note, you can't add or remove a Middleware
once it is provided to a StateReserve
.
Unlike Redux
, where the impure stuff like logging, IO tasks and any async tasks happen with the help of Middleware
based concept redux-thunk
, In Flywheel, those tasks are taken care of by SideEffect
. So, Middleware
in Flywheel is optional.
There are three functions available in a Middleware
:
-
next(action: Action)
: Callingnext
will pass on theAction
to the nextMiddleware
. -
dispatch(action: Action)
: Callingdispatch
will again start the loop from the beginning. i.e, if there are 5 middlewares and if the 3rd middleware callsdispatch
, again theAction
will pass through all the middlewares from the beginning. -
getState()
: Provides access to currentState
of theStateReserve
. In middleware,awaitState()
suspend function is not provided, which is available inSideEffect
. (awaitState()
guarantees to provide aState
that is updated by all the Actions in the queue.)
A simple skipMiddleware
implementation that will swallow Action
that implements SkipReducer
.
val skipMiddleware: Middleware<State> = { dispatch, getState ->
{ next ->
{
if (it !is SkipReducer) {
next(it)
}
}
}
}
Instead of defining Middleware
function as a variable, you also extend BaseMiddleware
to create a more complex Middleware
.
class CounterMiddleware(
scope: CoroutineScope,
dispatcherProvider: DispatcherProvider,
) : BaseMiddleware<CounterState>(scope, dispatcherProvider) {
override fun handle(
action: Action,
state: GetState<CounterState>,
next: Dispatch,
dispatch: Dispatch,
) {
when {
action is SkipReducer -> {
//Swallow the action
}
action is CounterAction.ResetAction && state().counter == 0 -> {
//Swallow the action and dispatch a another new action
dispatch(ShowToastAction("Counter state is already 0"))
}
action is CounterAction.ForceUpdateAction && action.count < 0 -> {
//Modify the action and pass on this action
next(action.copy(count = abs(action.count)))
}
else -> {
//Pass on this action
next(action)
}
}
}
}
- If you want to swallow an
Action
i.e prevent an action from reaching reducer or even theStateReserve
's actualdispatch
function. Even if it is swallowed,hotActions
will still receive theAction
to broadcast toBaseHotSideEffect
. - If you want to change the
Action
, For example:If you want to set the time indata class UpdateItemAction(val id: Int, val time: Long? = null): Action
UpdateItemAction
based on the availableState
inStateReserve
. You can do:next(action.copy(time = getState().time))
But, if you rely on outside sources like IO or async task to get the time, we recommended using a SideEffect
. Since any additional
operation done on middleware will increase the time it takes for the Action
to reach the reducer.
Anything, other than the above-mentioned use cases, you will be better off with a SideEffect
.
Middleware
is not bound to Dispatchers.Default
coroutine context. So, if a Action
is dispatched from UI layer, the Middleware
runs on Dispatchers.Main
coroutine context or if the Action
is dispatched from a SideEffect
, the Middleware
runs on Dispatchers.Default
. Basically, it runs in the context of caller.
- A middleware should always call
next()
unless themiddleware
wants to swallow theAction
. For example, an unauthorizedAction
. - A middleware should call
next()
only once. - A middleware can use
dispatch()
to send anAction
to reducer. Callingdispatch()
is optional.