Reductor - Redux for Android. Part 2: Composing reducers
The article is part of series about Reductor library – Redux implementation for Android.
In Part 1 we covered what is Reductor and how to use it to model state transitions as pure functions. We also used a feature of Reductor called “AutoReducer” to keep reducers type-safe and clean.
We ended up with reducer that looks like this:
Today we are going to use our approach to storing multiple datasets in Reductor
In the previous article, we developed our Android app using Redux pattern.
For our application, the instance of
Store was a Single source of truth.
Store was holding and managing a single piece of data as a state –
But we live in real world where requirements change every day. And as experienced android developers, we want to be able to add features to our application easily.
So what if we need to add one feature to our TODO application: allow the user to filter todo items by status.
As filter is just one of three options, we can model it as storing one of defined enum values:
But how can we store current filter?
Store class is designed to store only one state object.
The simplest way that might come to mind is to create separate
To do that we would need to create one more
We will call it
Using @AutoReducer it should be pretty straightforward.
Then we create corresponding
As it might look OK-ish initially, this idea is not as good as it seems. By having two different stores we’ve lost the important property of our application – Single source of truth. It means that each time we want to dispatch the action, we need to know which store will handle it. That’s not good.
Any other way? Yes!
One of 3 principles of Redux
says that whole state should be represented as one tree with a single store.
That means in our case we need to do is to create one more class to hold both
Ok, nothing complex. One holder class to store both our sub-states. The class is also immutable to prevent unexpected mutations (see Redux principle 2, Part 0).
But as we changed type our state from
AppState, reducer needs to be changed as well.
One can easily be created by merging
TodoReducer actions into one class.
Each action handler should also be updated to take and return
That looks good.
Now we can handle both actions related to
Even more, the structure with container class
AppState allow us to add any arbitrary sub-states and action handlers for them.
However, this approach has some downsides:
- Particular sub-states need to be unpacked from
AppStatebefore processing, and then packed back to new
- When application will grow, reducer can become “God reducer” with all the actions we can perform on the whole state.
Let’s see how we can sort it out.
Our reducer now handles all the actions we have in our application.
But we still want logically separate actions that only relate to
List<TodoItem> from actions that handle
To do this we need to remind ourselves that
Reducer is still a function with signature
(state, action) => state.
And what we, as programmers are taught to do? – Right! Compose functions.
If we will decompose our
AppStateReducer to smaller functions to compute each sub-state it will look like this:
This way we can manage how we can reduce each particular sub-state. But hey, look closer at this function:
List<TodoItem> items = reduceItems(state.todoItems, action);
Looks familiar, isn’t it? Is it a…
Why not reuse reducer from the beginning of the article. This way we can have our reducers small and concise.
That looks fancy.
Our main reducer contains only the logic of dispatching actions to “sub-reducers” and combining the result. That’s the all it needs to know.
On the other hand, our “sub-reducers” only handle the logic related to particular “sub-state”. And they know nothing about the outer state.
That’s what I call composition.
What we have done is already good enough. But can we do it even better? Sure!
Take a look at our final
Looks boring, doesn’t it?
For the particular structure of state class, the structure of reducer will be pretty obvious. But do you know any tool to help to write boilerplate code for us? – Right! Annotation processor.
That’s why Reductor has @CombinedState annotation included.
To take advantage of annotation processor, we need to define our combined state as interface annotated with
AppState will look like this,
reductor will generate the corresponding reducer:
The generated class will looks very similar to what we wrote by hand, except:
- Reducer includes
Builderto make it more convenient to provide sub-reducers;
- Reducer does null-check on
stateto prevent null pointer exception (Why it matters I’ll cover in one of the next articles);
- Before returning new state back, reducer do identity check for all the fields. And if all values are equals to original – we can safely return current state.
Therefore instantiation of reducer will be straithforward:
Customizable @CombinedState class.
As you can see from the example,
AppState is defined as an interface.
That means that Reductor will actually generate two things:
- Implementation of
AppState(which will be called
- Implementation of
Reducer<AppState>we saw before.
The implementation of our interface is just simple POJO, even without
But what if we need to make it
What if we need to use this with Gson or just make
toString() produce something human readable?
That’s a big bunch of features to support.
However, there is already a project which specialises on generating value classes. I’m talking about AutoValue. If you not familiar with it, check out Github page and documentation for more information.
1.2 AutoValue supports extensions, so value classes can have suppot for
Moshi and much more.
And the good news is that Reductor support
AutoValue classes as
@CombinedState out of the box!
With AutoValue, our
AppState will be defined as
Current @CombinedState support
At the moment (Oct 23, 2016) Reductor support
@CombinedState to be implemented either by
- AutoValue class
Support for Kotlin data classes is planned.
The idea of reducer as a pure function is powerful because of the ways it can be composed. Keeping your reducers small and focused makes it’s easy to manage and reason about them.
Taking this into account carrying all your application state tree in one object is just matter of combining smaller reducers. And with help of Java annotation processor, we can make this even easier.