Redux for Android with Kotlin in practice, Part 1: Initial setup

Alla Dubovska
xorum.io
Published in
4 min readSep 23, 2019

Choosing the right architecture for the Android app is a long story to tell. So I won’t be writing about all possible options and which one is theoretically better in this article. Instead I’ll show you how we applied Redux pattern to real-world application, which is already on Google Play for more than a year and have almost (OMG) 100 users.

Codeforces Watcher is an open-source Android application dedicated to users of quite famous platform for competitive programming — Codeforces. There are hundreds of contests each week with thousands of participants. Tasks are varied from quite basic to really advanced.

For now Codeforces Watcher allows users to see upcoming contests and track the activity of chosen users on the platform. We were about to add a few new features, but to make it easier, we decided to apply the right pattern first.

Meet Redux

The idea of Redux is very simple: let’s put all the data we need in app into one place (State), all logic in another (Reducer) and see how the magic happens. Of course, in practice, it’s a little bit more complicated, but the idea is just that.

So we have a global immutable State kept in a global Store, which allows us to subscribe for changes of this State in our Views. Views doesn’t contain any logic besides rendering State on the screen.

All logic in Redux pattern resides in Reducers, which are the only classes allowed to change the “immutable” State. Sounds simple? Let’s try to apply this in real-world application.

Upcoming Contests

It’s obvious we need some simple feature to start the migration to Redux. Let it be the “Upcoming contests” feature, which fetches all contests from Codeforces API, filter and sort upcoming ones, save them to Room and then shows on the screen using LiveData.

To migrate all this to Redux, we’ve made only 10 commits with 236 additions and 100 deletions. You can see all this in following PR. We will go through all the commits one by one and explain them in details.

Initial setup

Commit: Initial version of Redux pattern for Contests.

First of all, we need to import ReKotlin library, which implements basic classes of Redux pattern with Kotlin-idiomatic approach. This is as simple as:

implementation ‘org.rekotlin:rekotlin:1.0.4

Store

Then we need to setup our global store, which is responsible for keeping AppState up-to-date by using Reducers and Middlewares, which we can specify right in place. Usually state is composed in such a way, that it can be created with all default fields.

val store = Store(
reducer = ::appReducer,
state = AppState()
)

State

AppState contains all the data we need in the application. It can have unlimited number of sub-states, usually one for feature. For “Contests” feature, we create ContestsState, which contains only one field — list of contests.

data class AppState(
val contests: ContestsState = ContestsState()
) : StateType
data class ContestsState(
val contests: List<Contest> = listOf()
) : StateType

Voilà, we have our initial state, which is empty for now. It’s not very useful, so let’s put there some data. But state is immutable, isn’t it? Yes, but there is a special mechanism for doing this.

Reducers

State can be changed only in Reducers. Repeating the approach with AppState, we have main appReducer, which updates AppState using sub-reducers, which are responsible for each sub-state.

fun appReducer(action: Action, state: AppState?): AppState {
requireNotNull(state)
return AppState(
contests = contestsReducer(action, state)
)
}
fun contestsReducer(action: Action, state: AppState): ContestsState {
var newState = state.contests
when (action) {
is ContestsActions.ContestsLoaded -> {
newState = newState.copy(
contests = action.contests
)
}
}
return newState
}

Looks very simple, yes? But wait, where ContestsActions.ContestsLoaded is coming from?

Actions

Obviously, we need a medium to bind our Views to the AppState and Reducers. Such a mechanism is implemented through Actions, which can be dispatched from any place in our app and then being caught by Reducers or / and Middleware.

All you need to declare the action is implementing Action interface, like this:

class ContestsActions {data class ContestsLoaded(val contests: List<Contest>) : Action}

Outer ContestsActions is optional, but can be handy for grouping all feature actions in one place.

Views

And finally, let’s bind all this to View. In our case it’s ContestsFragment. Let’s do next changes.

It’s needed to implement StoreSubscriber interface, which is subscribed to ContestsState. To actually get some data from Store, it’s needed to subscribe for state and then unsubscribe from it, when it’s not more needed.

override fun onStart() {
super.onStart()
store.subscribe(this) { state -> state.select { it.contests } }
}
override fun onStop() {
super.onStop()
store.unsubscribe(this)
}
override fun newState(state: ContestsState) {
contestAdapter.setItems(state.contests.sortedBy(Contest::time))
}

Take a look on how we subscribe to the sub-state by using state.select method.

To keep commits small and atomic, we won’t refactor ContestsFragment further and will only make the finishing stroke — dispatch ContestsActions.ContestsLoaded action, when contests are loaded. Database operations are just commented for now, we will implement them later.

store.dispatch(ContestsActions.ContestsLoaded(contestList))

To be continued…

In this part we’ve covered only the initial step of migration to Redux pattern, but it’s already functional and can be compiled and run. In following parts we will see much more interesting features of Redux and how to apply them in real projects.

--

--

Alla Dubovska
xorum.io

Software engineer (👩‍💻 native iOS development), Mom 👦, Marathon finisher 🏃🏻‍♀️