How to handle asynchronous operations with Redux in Swift
All apps today need at some point to talk to a remote resource like a server or database. These are async operations, meaning they get executed on a secondary thread and return at some later time on the main thread.
Let’s say we want to connect to such a remote resource in order to fetch details about a certain movie. The model we want to get back is this:
And — to keep in line with functional code we’ve used in the past — we’re going to write a function to return that model with the help of a callback, like this:
Redux however is focused on sync operations. Reducers are pure functions with no side effects, the Store dispatches actions on the main thread, etc. This is what makes code deterministic, which means it’s easier to test and simpler to understand.
How then can we create a remote layer that works well with Redux?
First, let’s define what a good solution would look like:
- it should be testable
- it should be mockable
- it should be loosely coupled with other parts of the app
A good definition for all of the above already exists in the wider Redux ecosystem and it’s called a Middleware.
Essentially a middleware is an entity that allows you to perform side effects when a certain action passes through the Store. Those side effects can range from logging to the console, sending fire-and-forget stats as well as triggering new actions to the Store.
That last property of a Middleware is what we’ll be focusing on.
The system we want to build will listen to a particular Action, call the api to obtain a new Movie, and finally dispatch an Action containing the result.
For start, we’ll need to define what a Middleware looks like. As usual, we’re going to define it as a type alias for a function type.
If the concepts of ReduxState, Action, etc, are new to you, feel free to checkout my two other posts about Redux in Swift.
Then we’ll need to enhance our initial Store to allow it to handle Middlewares.
What we’ve essentially done is added an array of Middlewares the Store needs to be initialised with and a way to actually trigger them once a new state gets computed.
With that preparation work behind us, we can focus on the actual Actions we need to intercept and then dispatch.
You see we’ve split the MovieAction enum in a very granular way.
The start action marks the beginning of a remote fetch. This should be dispatched from a Props entity.
The success action marks the end of a remote fetch that has gone well. This will be dispatched by the Middleware we’re write.
The end action marks the end of a remote fetch that hasn’t gone well. This too will be dispatched by the new Middleware.
Finally, we can use the rules above to create the new movieMiddleware:
To tie it all up, this is how we’d add this to our Store.
The beauty of this is that it allows us to define our whole app business logic with nothing but Reducers and Middlewares. For example, a more mature movie app might have the following list of middlewares:
And that’s it!
This article is one of a multi-part series exploring State, UI and Redux and how we can leverage knowledge of these topics to write more modular and testable code in both iOS and Android.