Open sourcing RxRedux

Recently we have open sourced RxRedux a Redux implementation powered by RxJava. In this blog post we discuss why we at Freeletics have built RxRedux, how it works, what the difference is to other existing Redux inspired libraries, how to isolate side effects and how RxRedux helps to build a state machine based business logic.

State machine based architectures

State-focused architectural design patterns such as Model-View-Intent (MVI) have seen a recent surge in popularity. We don’t have to talk about the benefits of proper state management and unidirectional dataflow again as there are already many blog posts out there describing them. While the benefits are clear implementing such an architecture is still hard. In the web world Redux is the golden standard. The idea of Redux is simple: Action, Reducer and an observable Store to glue Action and Reducer together. Over the last years Redux inspired libraries for Android have been published like Mobius from Spotify, Grox from Groupon or Suas from Zendesk.

RxRedux

RxRedux is heavily inspired by redux-observable which is a so called “middleware” for Redux. A middleware is basically a way to deal with async Actions (we will describe what an Action is later). This is where RxRedux comes into play. RxJava is an already well established library to deal with async event streams. Hence RxJava is a great choice to deal with async Actions. In fact other libraries like Mobius provide optional RxJava plugins to leverage the power of RxJava for async Actions. RxRedux is different compared to other libraries out there: Not only is RxJava a first class citizen, RxRedux is entirely based on RxJava. Actually, RxRedux is a RxJava operator: .reduxStore(). You can think of .reduxStore() as a replacement for RxJava’s .scan() operator (used heavily in MVI). Overall the target picture looks like this:

RxRedux

ReduxStore

A Store is basically an observable container for state. RxRedux is entirely written in Kotlin and offers an extension function .reduxStore<State, Action>(initialState, sideEffects, reducer) for RxJava’s Observable<T> to create such a container for state. It takes an initialState and a list of SideEffect<State, Action> and a Reducer<State, Action>. The following section describes the building blocks of RxRedux: SideEffects, Actions and Reducer.

Action

An Action is a command to “do something” in the Store. An Action can be triggered by the user of your app (i.e. UI interaction like clicking a button) but an Action can also be a system notification like battery level of the users device has changed or sensor data like GPS position updates. Additionally, a SideEffect can trigger Actions too. Furthermore, SideEffects can be registered for a certain type of Action to “do something”. We will talk about SideEffects in a minute. Important is to note that every Action goes through the Reducer.

Reducer

A Reducer is basically a function (State, Action) -> State that takes the current State and an Action to compute a new State. Every Action goes through the Reducer. An Action can be “ignored” by the Reducer by simply returning the previous state (which means state has not changed). Such “ignored” Actions are usually handled by a SideEffect. We will see in the example below what this exactly means. For now it’s just important to note that every Action goes through the Reducer and the Reducer is responsible to compute the new State.

Side Effect

A Side Effect is a function of type (Observable<Action>, StateAccessor<State>) -> Observable<Action>. So basically it’s Actions in and Actions out. You can think of a SideEffect as a use case in clean architecture: It should do just one job. Every SideEffect can trigger multiple Actions (remember it returns Observable<Action>) which go through the Reducer but can also trigger other SideEffects registered for the corresponding Action. Also an Action can have a payload. For example if you load some data from backend you emit the loaded data as an Action like data class DataLoadedAction (val data : FooData). The mantra an Action is a command to do something is still true: in that case it means data is loaded, do with it “something”.

StateAccessor

Whenever a SideEffect needs to know the current State of the the Redux Store it can use StateAccessor to grab the latest state from Redux Store. StateAccessor is basically just a function () -> State (think fun getState() : State).

Example: Pagination

Let’s create a simple android application that displays a list of popular Github repositories. As the user of this app scrolls to the end of the list, the next page of popular Github repositories is loaded. The real deal with RxRedux are SideEffects (Action in, Actions out) as we will try to highlight in the following example (source code is available on Github).

To set up our Redux Store with RxRedux we use .reduxStore():

// Actions triggered by the user in the UI / View Layer
val actionsFromUser : Observable<Action> = ... 

actionsFromUser
  .reduxStore(
    initialState = State.LOADING,
    sideEffects = listOf(::loadNextPageSideEffect, ::showAndHideErrorSnackbarSideEffect, ... ),
    reducer = ::reducer
  )
  .distinctUntilChanged()
  .subscribe { state -> view.render(state) }

For the sake of readability we just want to focus on two side effects in this blog post to highlight how easy it is to compose (and reuse) functionality via SideEffects in RxRedux but you can check the full sample code on Github where you also find the code for the Reducer.

fun loadNextPageSideEffect(actions : Observable<Action>, state : StateAccessor) : Observable<Action> =
  actions
    .ofType(LoadNextPageAction::class.java)
    .switchMap {
      val currentState : State = state()
      val nextPage : Int = currentState.page + 1

      githubApi.loadNextPage(nextPage)
        .map { repositoryList ->
          PageLoadedAction(repositoryList, nextPage) // Action with the loaded items as "payload"
        }
        .startWith( StartLoadingNextPageAction )
        .onErrorReturn { error -> ErrorLoadingPageAction(error) }
    }

Let’s recap what loadNextPageSideEffect() does:

  1. This SideEffect only triggers on LoadNextPageAction (emitted in actionsFromUser)
  2. Before making the http request this SideEffect emits a StartLoadingNextPageAction. This action runs through the Reducer and the output is a new State that causes the UI to display a loading indicator at the end of the list.
  3. Once the http request completes PageLoadedAction is emitted and processed by the Reducer as well to compute the new state. In other words: the loading indicator is hidden and the loaded data is added to the list of Github repositories displayed on the screen.
  4. If an error occurs while making the http request, we catch it and emit a ErrorLoadingPageAction. We will see in a minute how we process this action (spoiler: with another SideEffect).

Here is a video that visualizes the flow of Actions. The blue box is the View (think UI). The Presenter or ViewModel has not been drawn in this diagram for the sake of readability but you can think of having such additional layer between View and ReduxStore (Redux State Machine). The yellow box represents a ReduxStore. The grey box is the Reducer. The pink box is a SideEffect, here it represents loadNextPageSideEffect(). Additionally, a green circle represents State and a red circle represents an Action. All Actions go through the reducer, but Reducer might not change state on every incoming Action but rather return previous state. That is the case on LoadNextPageAction which is “ignored” by reducer (represented in different red color) because it actually triggers loadNextPageSideEffect(). On the right you see a UI mock of a mobile app to illustrate UI changes.

The state transitions (for the happy path - no http networking error) are reflected in the UI as follows:

RxRedux

Next, let’s discuss how to handle the http networking error case. In RxRedux a SideEffect emits Actions. These Actions go through the Reducer but are also piped back into the system. That allows other SideEffects to react on Actions emitted by a SideEffect. We do exactly that to show and hide a Snackbar in case that loading the next page fails. Remember: loadNextPageSideEffect emits a ErrorLoadingPageAction.

fun showAndHideErrorSnackbarSideEffect(actions : Observable<Action>, state : StateAccessor) : Observable<Action> =
  actions
    .ofType(ErrorLoadingPageAction::class.java) // <-- HERE
    .switchMap {action ->
        Observable.timer(3, TimeUnit.SECONDS)
          .map { HideLoadNextPageErrorAction(action.error) }
          .startWith( ShowLoadNextPageErrorAction(action.error) )
    }

What showAndHideErrorSnackbarSideEffect() does is the following:

  1. This side effect only triggers on ErrorLoadingPageAction.
  2. We show a Snackbar for 3 seconds on the screen by using Observable.timer(3, SECONDS). We do that by emitting ShowLoadNextPageErrorAction first. Reducerwill then change the state to show Snackbar.
  3. After 3 seconds we emit HideLoadNextPageErrorAction. Again, the reducer takes care to compute new state that causes the UI to hide the Snackbar.

RxRedux

Confused? Here is a (pseudo) sequence diagram that illustrates how Actions flow from SideEffect to other SideEffects and the Reducer:

RxRedux

Please note that every Action goes through the Reducer first. This is an explicit design choice to allow the Reducer to change state before SideEffects start. If Reducer doesn’t really care about an action (i.e. ErrorLoadingPageAction) Reducer just returns the previous State. That’s what we meant with “Action ignored by Reducer” in previous sections.

Of course one could say “why do you need this overhead just to display a Snackbar”? The reason is that the whole UI state comes from a single source of truth (Redux Store). Moreover, now showing and hiding a Snackbar is easy to test. Furthermore, showAndHideErrorSnackbarSideEffect() can be reused. For Example: If you add a new functionality like loading data from database, error handling is just emitting an Action and showAndHideErrorSnackbarSideEffect() will do the magic for you. With SideEffects you can create a plugin system of reusable components.

Conclusion

The aim of this blog post was to show how RxRedux can be used together with SideEffects to break your business logic into loosely coupled, reusable and composable components. Unidirectional dataflow and proper state management makes your app deterministic and easy to test (see the tests for the sample app Github). RxRedux can be handy if you already use RxJava, like we at Freeletics do, to create state machines for your app.