The article is part of series about Reductor library – Redux implementation for Android.

Motivation

As an Android engineer I’m happy to see how this field of software engineering is changing over time. Each year our set of tools and approaches evolve and improve. However there is one particular problem we fight every day, and each time we do it differently. Today we going to talk about state management.

State management

Any android application stores and manages some state, either implicitly or explicitly. The more data application holds, the harder it gets to understand what’s going on. Especially when it lives in multithreading asynchronous environment.

For instance remember how many solutions exist to preserve state across orientation change, and neither of them is ideal. So are we doomed, or is there a better way? I believe there is at least one!

Another field of software engineering with very similar problems is frontend development. Nowadays SPA are client side applications which interact with the user, talk to the server, manage some state and do all of these asynchronously as well as android applications. So maybe they have some better tools in the toolbox? And it seems like they do.

Redux

Redux Logo

Redux is predictable state container for JavaScript apps. Redux can be described in three fundamental principles:

  • Single source of truth
  • State is read-only
  • Changes are made with pure functions

Redux based on ideas both from Flux and Elm Architecture. It has a big community with a lot of third party extensions, tutorials and examples for a quick start.

If you familiar with Javascript syntax, I strongly recommend reading about the whole idea from nice Redux documentation. Or you can watch this 10 minutes video by Christina Lee on how we can adopt some of the web approaches in Android.

Reductor is basically my reimplementation of Redux in Java.

I’ll not focus on how Redux or Reductor is implemented in this article (but I’ll do it in next one). However, for better understanding the problem, I’ll provide a simple example here and show how we can improve our code without any of additional tools.

Learn by example

To show the idea I will use a simple example of Todo list (which is a canonical example in Redux community). We will create a simple application with a naive approach (using a lot of mutable states) and then refactor it to make it easier to understand and maintain.

Our Todo app will have few features:

  • Displaying a list of Todo items with text and checkbox
  • User can add Todo items to list
  • User can change status of each item by switching the checkbox

Note: I’ll omit most of the android related boilerplate code to focus only on state management

Naïve solution

Here is our model definition:

And class which will store and manage Todo items. Let’s call it TodoStore:

As you can see these classes are simple POJO with getters and setters. Now imagine how client code would look like to work with this state:

Mutable state

Ok, we have built our first implementation. This code is simple but fragile! Here are few obvious disadvantages:

  • Manual adapter notification. In any place we change the state, we need to manually notify adapter to reflect changes in UI
  • Data can be mutated when we do not expect this
  • Not thread safe
  • Hard to test

Let’s try to refactor this solution a bit

State listener

Instead of notifying adapter manually we can create a listener to notify an adapter automatically each time we change the state

Key changes:

  • Added StateListener interface
  • Added method addListener to register listener for state updates
  • Notify all listeners each time we change the state

Now we can remove manual calls to adapter.notifyDataSetChanged()

But in fact this code will not work as expected, because we made one bug: In TodoStore we notify the listeners by calling setTodoItems, however, in our MainActivity code we mutate the list we get from getTodoItems directly. As you can see it’s easy to introduce a bug, when working with the mutable state. Therefore we need to fix it.

Encapsulation

As we saw in the previous example we can still accidentally mutate our data without notifying listeners. We can fix it by encapsulating all the logic of managing the state inside the TodoStore.

Immutability

So now we have no other way to change the state except defined methods in TodoStore… Well, almost. We can still mutate each TodoItem and modify List of items received by getTodoItems(), let’s fix them.

Here is immutable TodoItem. Now we can get rid of getters and setters as we have final fields;

Also, we need to change implementation of TodoStore to create new object every time we change the list Note: instead of copying lists by myself I used Pcollections as an implementation of persistent/immutable collections.

Good! Now each time we get List of TODO items, we know that object is immutable, so we can be sure nobody can change it out of the contract (methods defined in TodoStore). That’s already better than our first naive implementation. But we can go even further with that.

Improving testability

Our code still has some disadvantages. The main one: this code is not testable enough. For instance, to test function changeState we need to have at least one item in the list, so in tests, we need to ‘pre-initialize’ the state first before calling the function. Then we need to get list of items via getTodoItems() to check if it satisfy test conditions. In our case, it’s not possible without additional constructors/methods for test purpose.

But let’s take a small step back. We know that our data is immutable now as we always create new state instead of mutating previous one. Each time we want to change the state we take current state, compute new state and re-assign the variable. What if we extract this computation step as a pure function that takes current state and returns next state.

So in result, we will get this structure of TodoStore

As we can see we extracted logic of transition from old to a new state into pure functions, so they can be tested easily. In fact, our public functions become pretty similar. All they do now:

  • Compute a new state from the old state and additional data passed externally as parameters
  • Reassign a state variable
  • Notify listeners

Conclusion

At this point, we can stop refactoring, as this solution is already better than our initial naïve implementation. However, we managed to improve state management only for one particular dataset in our application – list of Todo items. In real life, our application will manage much more data. So…

Should we apply this approach for other datasets? – Yes!

Should we reimplement Store for each dataset? – No! We can try to generify our solution to use with any data.

But that’s not as easy as to extract a type parameter. We will work on it in the next part of this series. But if you are curious what we will talk about next time take a look into the implementation of Reductor.

Stay tuned. To be continued…

Part 1: Introduction