Writing a Todo app with Redux on Android
Android community seems to be actively looking for the right architecture for their apps. We’ve passed through the chaos of findViewById and setting the listeners manually, we’ve embraced ButterKnife and now we’re carefully looking at Google Data Binding library.
Here at Trikita we like to discover new paths. We’ve made Anvil when there was no sane UI library that would handle data binding. Now Anvil is pretty mature and works well with both Java and Kotlin. It’s not an official Google solution, but we all know that not every library sold by Google is worth it.
In the terms of Redux, “state” is an object representing your app data. For our Todo app it’s basically a list of tasks. Each task will have an identifier, a name and a checked/unchecked flag. More complicated apps could use compound objects to describe their state.
Redux strongly advises to use immutable states. Immutable objects are easier to deal with — they are thread-safe and garbage collector is happier to deal with transient smaller immutable objects than the rarely released large ones. So far we will be using Immutables library to avoid boilerplate of writing immutable classes in Java:
Actions are actually just the events emitted by the UI components or some other part of your app (e.g. services). Actions are used to modify the state. Action classes are simple POJOs and containing only some minimal data required to identify a certain state modification. Again, Jedux doesn’t force you to use certain Action classes — you may use Enums or Strings or integer constants to identify your actions. But for your convenience there is a pre-defined Action<T,V> class that has Enum action type T and a payload object of class V.
In our Todo app the user should be able to add tasks, toggle their state, delete individual tasks or clear all completed tasks at once.
This gives us the following enumeration of Action types:
Most actions will be carrying a payload (“Add” requires a task name, “toggle” and “delete” take a task ID). That’s why we will be using a standard “Action<ActionType, V>” class from Jedux library here.
Reducer is a function (often a method reference or lambda) that takes current State, incoming Action and returns a new modified State instance depending on the action.
Reducer is often a simple switch/case dispatching different Action types. To avoid bloated reducers you may apply the technique described here.
One thing to remember is that reducer must not have any side-effects. It must not start services, or show notifications, or save data to disk — it’s a pure function that simply modifies the state.
So far you’ve seen application specific classes and may wonder what actually Jedux does for you. Jedux provides a single class used to tie your State, Reducer and Actions together. It’s called Store.
Store is often global (either use DI, or make it a singleton, or just pass it from top to bottom). Store has two methods:
- getState(): State — returns current state instance.
- dispatch(Action): State — triggers an action and returns the new state after the action is processes.
- subscribe(Runnable) — a Runnable will be called every time the state has been modified. Good for updating UI. This method returns another runnable that unsubscribes the listener.
When the store is created you must pass a reducer and the initial state instance into it. Also you may pass any number of Middlewares, but we’ll discuss them later.
View layer binding
At this point you should be able to already test your app by emitting actions and verifying your state data. But you obviously want to see how it looks like.
We often start quickly with the bare views and see how the app “feels” in your hands. And this is where Anvil enters the game:
Here mTasksAdapter is an implementation of RenderableAdapter from Anvil, it uses the same layout syntax and data binding principles for its child views as the example above:
Styling your views
Anvil provides an amazing power of Java or Kotlin to refactor and reuse your view styles. You may use constants, expressions, inheritance or functional compositions as described here.
We’ve made a separate class called “Style” and soon our UI looked and worked like this:
This is the key to making useful apps. You may wonder — where should I put my services, network synchronization, database operations or calling other android APIs?
The answer is Middleware. Middleware is a class that intercepts Actions and can either modify them, or handle them internally, or even emit other Actions instead.
Since your app is now a flow of actions — no change would happen without an action emitted. Let’s see how Middlewares can enhance our Todo app.
The behavior of your app is now fully defined by actions and state. A Logger middleware can help you with debugging when things do wrong. It simply prints every action and state value to logcat:
Obviously we don’t want Todo app to lose the tasks after it is closed. So let’s just put our State into a persistent storage after every single action and that would become our initial state value for the next app run.
Luckily, Immutables library generates good JSON type adapters, so the persistence layer becomes very simple.
The sources of this Todo app are available on Github :
You may communicate with your services from middleware, do networking operations, perform asynchronous operations etc etc. We strongly recommend to keep middlewares small and focused. It simplifies testing a lot.
Speaking of testing, Redux apps are easy to test because you can:
- Test your reducer alone by sending actions and asserting state values (actions and state may not be tested since they contain no methods).
- Test your middleware by sending actions and doing asserts.
- Test your UI with Espresso and other tools by mocking the state. Optionally you may mock the reducer and catch the actions ensuring that views emit them correctly.
Android purists may disagree with the beauty of Redux because it enforces having a global state. But in practice it turns out to be a big simplification for developers and the apps become much faster to design and develop.
Also remember that Redux (especially Redux for Android) is still a new paradigm. There is a huge open field for experiments so next time you would like to try something new — let it be Redux. And don’t forget to share your discoveries with us!