Oct 23, 2016
The article is part of series aboutReductor library –Redux implementation for Android.
InPart 1 we covered what isReductor 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 ReductorStore.
In the previous article, we developed our Android app using Redux pattern. For our application, the instance ofStore was aSingle source of truth. ThatStore was holding and managing a single piece of data as a state –List<TodoItem>.
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 separateStore forTodoFilter.To do that we would need to create one moreReducer. We will call itFilterReducer. Using@AutoReducer it should be pretty straightforward.
Then we create correspondingStore:
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 of3 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 bothList<TodoItem> andTodoFilter.
Ok, nothing complex. One holder class to store both our sub-states.The class is also immutable to prevent unexpected mutations (seeRedux principle 2,Part 0).
But as we changed type our state fromList<TodoItem> toAppState, reducer needs to be changed as well.One can easily be created by mergingFilterReducer andTodoReducer actions into one class.Each action handler should also be updated to take and returnAppState.
That looks good. Now we can handle both actions related toList<TodoItem> andTodoFilter.Even more, the structure with container classAppState allow us to add any arbitrary sub-states and action handlers for them.
However, this approach has some downsides:
AppState before processing, and then packed back to newAppState;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 toList<TodoItem> from actions that handleTodoFilter.To do this we need to remind ourselves thatReducer 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 ourAppStateReducer 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…Reducer? Exactly!
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 finalAppStateReducer class.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 whyReductor has@CombinedState annotation included.
To take advantage of annotation processor, we need to define our combined state as interface annotated with@CombinedState.If ourAppState will look like this,
reductor will generate the corresponding reducer:
The generated class will looks very similar to what we wrote by hand, except:
Builder to make it more convenient to provide sub-reducers;state to prevent null pointer exception (Why it matters I’ll cover in one of the next articles);Therefore instantiation of reducer will be straithforward:
As you can see from the example,AppState is defined as an interface.That means that Reductor will actually generate two things:
AppState (which will be calledAppStateImpl);Reducer<AppState> we saw before.The implementation of our interface is just simple POJO, even withouttoString() andequals() defined.
But what if we need to make itSerializable orParcelable?What if we need to use this with Gson or just maketoString() 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 aboutAutoValue. If you not familiar with it, check outGithub page anddocumentation for more information.
Since version1.2 AutoValue supports extensions, so value classes can have suppot forParcelable,Gson,Moshi and much more.
And the good news is that Reductor supportAutoValue classes as@CombinedState out of the box!With AutoValue, ourAppState will be defined as
At the moment (Oct 23, 2016) Reductor support@CombinedState to be implemented either by
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.