In the previous articles, I talked about Redux architecture and the Role of middleware in Redux. There’s no better way to learn something than implementing it yourself. We are going to write a very lightweight implementation of Redux architecture in Kotlin which can be used for our android app.
Redux is an architecture for predictable state container and state management. It is inspired from Flux and uses some of its components. Let’s go through the important components of Redux in brief.
State in Redux describes your app’s state at any given time. State is an immutable object which holds the information about the ‘current state’ of the app.
Action is an object which describes a change in the state. Action may have some payload which is used to update the state.
Reducer is a pure function which changes the app state from State A to State B for a given action.
Store is at the center of Redux architecture. A store stores the app state and manages it. Once an action is dispatched, the stores updates the app state with the help of reducers.
A listener subscribes to the store to receive updates when the app state is updated. In your android app, a typical listener is your Activity, Fragment or View which receives the updated state and
renders a new UI.
Middleware are essential in Redux architecture to handle side effects which include logging, IO calls, network calls, etc. When an action is dispatched, the store sends the action to the middleware. The middleware does something (api call, etc) and updates the action and sends it back to the store.
For more details about the components of Redux, I would recommend that you check out the previous articles in the series.
Now that we are familiar with Redux, let’s start writing our own implementation.
Implement Redux Architecture in Kotlin
Kotlin is a feature rich language which gives it significant advantage over Java. Kotlin is now an official language for Android application development so it make sense to learn more about it and start writing our apps in Kotlin.
Let’s begin by defining our components. For convenience and ease of writing the code, we are going to use Kotlin’s higher order functions to represent some of the components.
Type aliases provide alternative names for existing types. If the type name is too long you can introduce a different shorter name and use the new one instead. So we are going to use
typealias to define Reducer.
Actionis just an interface. All our actions will implement the empty interface.
Reduceris a pure function. So we use typealias to define a method definition which takes
Actionas input and returns
Subscriptionis a typealias for a function which takes
Stateas input parameter. It does not return anything. This will be our
- We defined an interface for
Storewith a generic
State. In Redux, the whole app will have one instace of store and one App state tree object.
Let’s implement the store.
SimpleStoreis an abstract class which takes generic
State. To initialize a new instance, we pass an initial state and a list of reducers.
- When a subscription subscribes to the store, we add it to the list of subscriptions and notify it of the current state.
- When an action is dispatched to the store, the store reduces the current state with the help of reducers and notifies all the subscriptions if there’s a change in the state.
Let’s see an example of how our Redux architecture is going to work. For this example, I have chosen to go with an app that searches for movies. It has a
SearchFragment where user can enter a query in the edit text and press a button to begin the search. While, the search is ongoing, the screen will show a spinner and disable the search button. Once the results are available, the screen will hide the spinner and show the list of movies. To keep it simple, we are not going to focus on errors at the moment.
Let’s define a state for the search fragment. The actions associated with it and a reducer.
loading- whether the app is loading results or not,
query- query that the user entered,
movies- list of movies when the results are loaded.
ClearSearch- action is dispatched when user clicks on the clear button.
Search- action is dispatched when user clicks on the search button. The action has
queryas the payload.
LoadingSearch- action is dispatched when the api starts loading results. This action will be dispatched by middleware, we’ll implement it later.
SearchResultsLoaded- action is dispatched when the api returns results. It has payload of list of movies.
reduceSearchStateis our reducer function associated with this state and the screen. You may have multiple reducers for one state. Typically, we would write a reducer for that state, eg.
SearchState. But here, we have written it for
AppStatewhich is main state tree object and it contains
As you can see, the reducer is not at all tied with the view and it’s so much easier to write unit tests since it does not even depend on anything Android related.
Let’s define our store which we will use in the app. There should be only one instance of the store so we are going to use a singleton.
AppStateis our state tree object which contains the whole state of the app.
- We create a new class
AppStorewhich works with
AppState. We provide an initial state and a list of reducers. We include
reduceSearchStatereducer in the list.
Let’s move on to our View and write a render function.
- We subscribe to the store in
onViewCreatedand unsubscribe in
rendermethod takes the state and updates the view.
- The view sends appropriate actions when user performs some action.
Make the store more closed
Any piece of code can access
Store.instance and dispatch some action or get the current state to update something else. This breaks the unidirectional flow of Redux. It would become difficult for debugging so it’s better that we restrict the access to the store and its properties.
Dispatch: We want our store be a bit more restrictive and don’t want it to be accessed from some corner of the app. Let’s define Dispatch. It’s a higher order function passed by the store to its subscribers.
Unsubscribe: Similarly, we do not want some other part of the code to call
unsubscribe()to some other subscriber.
- We also restrict the code to access the state. If some component wants to access the state, it can subscribe to the store.
- We updated the definition of
Subscription. The method now takes
Dispatchas a parameter which the store will pass to its subscribers.
- When the view wants to unsubscribe from the store, it can just call
- To dispatch an action, the component can now use
Implement the new store
We have updated the definition of store and made it more restrictive. Let’s modify the implementation.
With this new definition of the Redux store, we just need to update somethings in our View and render function. The rest of the code does not change.
Update the View
Store.dispatch(action) method is no longer accessible, but the store sends
Dispatch function reference when it sends updates regarding the state. So let’s update our render function to have a reference of
- The fragment keeps a reference to
Dispatchand uses it to dispatch actions. It chucks away the reference in
- The fragment invokes
onDestroyViewlifecycle method to stop getting events from the store.
We have a working implementation of Redux architecture. If you see the implementation of
Store, it’s very simple and tiny. That is why I like this architecture. It may seem complex at first, but actually it’s really easy once you get your head around it.
If we revisit the flow, the view subscribes to the store for the state updates. The view dispatches actions to the store. The store sends the action and the current state to the reducer. The reducer updates the state if necessary. Once the store receives the updated state, it notifies the view of the update.
We are still missing a crucial component in this implementation -
Middleware. Without middleware, we could not make network calls, log some stuff, or do anything on the background thread. We’ll modify our implementation of the store to include Middleware and write an example of a middleware which makes an api call to get the search results.
Redux architecture series
- Introduction: Redux architecture for android apps
- Middleware: Introduction and implementation
- Write your own Redux implementation in Kotlin
- Add Middleware to your Redux implementation
- Build Battleship Game with Redux - Groundwork
Add Middleware to your Redux architecture implementation
Are you having a hard time figuring out how middleware works in Redux architecture? This article talks about adding middleware to your Redux implementation. This is the missing piece for having a great Redux implementation.