Middleware
The middleware is a Suas
component that gives you the power to customize Suas
dispatching logic. A middleware can be used to plug some code that gets executed after the action is dispatched to the Store and before it reaches the Reducer.

Middlewares can be used to add logging capability to Suas
, to perform asynchronous actions (like network calls), to store and load content from the disk, and much more.
What is a middleware
At its core a middleware is a class or struct that implements the Middleware
protocol or interface:
public protocol Middleware {
func onAction(action: Action,
getState: @escaping GetStateFunction,
dispatch: @escaping DispatchFunction,
next: @escaping NextFunction)
}
interface Middleware {
fun onAction(action: Action<*>,
state: GetState,
dispatcher: Dispatcher,
continuation: Continuation)
}
public interface Middleware {
void onAction(@NonNull Action<?> action,
@NonNull GetState state,
@NonNull Dispatcher dispatcher,
@NonNull Continuation continuation);
}
When creating the Store
we can optionally pass a list of middlewares to it. Actions
dispatched to the store are first sent to the middlewares. Each middleware can get the current state, dispatch additional actions, and decides whether it wants the current action to be sent to the reducer or next middleware or not.
The middleware onAction
function is called with four parameters:
- The current
action
that was dispatched to theStore
. getState
: a function that when executed returns the currentStore
state.dispatch
: a function that can be used to dispatch new actions. This is the samedispatch
function that theStore
defines. Callingdispatch
is analogous to callingstore.dispatch
.next
: a function that when executed will continue theAction
processing. Callingnext
will execute the next middleware, or it will execute thereducer
if there are no more middlewares to process.
Inside the onAction
the middleware can do the following:
- Get the current (old) state by calling
getState()
(before callingnext
) - Propagate the action to the next middleware (or the reducer if it's the last middleware) by calling
next(action)
- Decide to stop the action from being propagated by not calling
next
- Dispatch a new action before and after the current action. That can be done by using
dispatch
.
func onAction(action: Action,
getState: @escaping GetStateFunction,
dispatch: @escaping DispatchFunction,
next: @escaping NextFunction) {
// Action will be dispatched before the current action
dispatch(SomePreAction())
// Read the current old state. Before calling next the state is unchanged.
let oldState = getState()
// Continue the processing of the current action
next(action)
// Read the new state. After next the Store state is changed.
let newState = getState()
// Dispatch a final action after the current action
dispatch(SomePostAction())
}
override fun onAction(action: Action<*>,
state: GetState,
dispatcher: Dispatcher,
continuation: Continuation) {
// Action will be dispatched before the current action
dispatcher.dispatch(SomePreAction())
// Read the current old state. Before calling next the state is unchanged.
val oldState = state.state
// Continue the processing of the current action
continuation.next(action)
// Read the new state. After next the Store state is changed.
val newState = state.state
// Dispatch a final action after the current action
dispatcher.dispatch(SomePostAction())
}
@Override
public void onAction(@NonNull Action<?> action,
@NonNull GetState state,
@NonNull Dispatcher dispatcher,
@NonNull Continuation continuation) {
// Action will be dispatched before the current action
dispatcher.dispatch(new SomePreAction());
// Read the current old state. Before calling next the state is unchanged.
State oldState = state.getState();
// Continue the processing of the current action
continuation.next(action);
// Read the new state. After next the Store state is changed.
State newState = state.getState();
// Dispatch a final action after the current action
dispatcher.dispatch(new SomePostAction());
}
In order to get more accustomed to the middleware, let's implement a simple logging middleware.
Simple Logger Middleware
In this example, we will implement a simple logger middleware that logs the action receives, the old state and the new state. (Note: Suas already comes packaged with a more advanced Logger middleware).
class MyLoggerMiddleware: Middleware {
func onAction(action: Action,
getState: @escaping GetStateFunction,
dispatch: @escaping DispatchFunction,
next: @escaping NextFunction) {
// Print the action
print("Action receveived: \(action)")
// Read the state before any reducer changes it
print("Current state: \(getState())")
// Continue the dispatching process..until the reducer reduces the action
// Not calling `next` will prevent the action from reaching the reducer
next(action)
// Read the state after any reducer changes it
print("New state: \(getState())")
}
}
let store = Suas.createStore(reducer: ..., middleware: MyLoggerMiddleware())
class MyLoggerMiddleware : Middleware {
override fun onAction(action: Action<*>,
state: GetState,
dispatcher: Dispatcher,
continuation: Continuation) {
// Print the action
print("Action receveived: " + action)
// Read the state before any reducer changes it
print("Current state:" + state.state)
// Continue the dispatching process..until the reducer reduces the action
// Not calling `next` will prevent the action from reaching the reducer
continuation.next(action)
// Read the state after any reducer changes it
print("New state:" + state.state)
}
private fun print(message: String) {
//...
}
}
// ...
val store = Suas.createStore(reducers)
.withMiddleware(MyLoggerMiddleware())
.build()
public class MyLoggerMiddleware implements Middleware {
@Override
public void onAction(@NonNull Action<?> action,
@NonNull GetState state,
@NonNull Dispatcher dispatcher,
@NonNull Continuation continuation) {
// Print the action
print("Action receveived: " + action);
// Read the state before any reducer changes it
print("Current state:" + state.getState());
// Continue the dispatching process..until the reducer reduces the action
// Not calling `next` will prevent the action from reaching the reducer
continuation.next(action);
// Read the state after any reducer changes it
print("New state:" + state.getState());
}
private void print(String message) {
//...
}
}
// ...
Store store = Suas.createStore(reducers))
.withMiddleware(new MyLoggerMiddleware())
.build();
Let's break down the middleware above:
- We print the
action
we received in the middleware. - Calling
getState()
, if called before callingnext
, returns the current state in theStore
. - In order for the middleware to propagate the call to the next middleware, or to the reducer, we Must call
next(action)
. - After calling
next(action)
the state in theStore
is changed. CallinggetState()
returns the newStore
state.
Using Multiple Middlewares
When creating the store you can pass multiple middlewares by combining them with the +
operator in iOS, or list them separated by comma in Android.
let store = Suas.createStore(reducer: MyReducer(),
middleware: MyLoggerMiddleware() + MyOtherMiddleware())
val store = Suas.createStore(MyReducer())
.withMiddleware(MyLoggerMiddleware(), MyOtherMiddleware())
.build()
Store store = Suas.createStore(new TodoReducer())
.withMiddleware(new MyLoggerMiddleware(), new MyOtherMiddleware())
.build();
When an action is dispatched, it's first sent to the MyLoggerMiddleware
. In calling next(action)
in MyLoggerMiddleware
onAction
function propagates the action to the MyOtherMiddleware
. Calling next
on the latter finally propagates the action to MyReducer
.
If any of the middleware chooses to not call next(action)
in its onAction
function, the action is stopped from propagating and will not reach MyReducer
.
What's Next
Suas
provides three middlewares out of the box:
LoggerMiddleware to logs the action and the state.
MonitorMiddleware to monitor state changes and actions dispatched.
AsyncMiddleware to process asynchronous actions.
More advanced topics
Applications with multiple states
Async Actions
Filtering Listener
Using the StateSelector
Updated over 6 years ago