Taming state in Android with Elm Architecture and Kotlin, Part 3

Sergey Grekov
ProAndroidDev
Published in
5 min readNov 30, 2017

--

In the Part 1 I introduced the basics of Elm Architecture and in Part 2 I spoke about its benefits and implementation in Android. In this Part I want to concentrate on more advanced issues — Subscriptions, composing Elm Architecture with Clean Architecture, and Time Travel for dessert!

In order to demonstrate all real world use cases and edge cases of using TEA in Android, I developed a sample open source application, which is reimplementation of translate app Yandex.Translate. All code samples and UI demos are borrowed from there. The source code is located here

Subscriptions

In the previous parts I described main functions of Elm Architecture lifecycle — Update, Render, Call. But I didn’t mention another fundamental function — Sub, short for Subscriptions. Subscription is like an Observable in RxJava but built in the heart of The Elm Architecture. So, this is how it looks like and how does it work in Elm?

type Msg
= Tick Time
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every second Tick

This code example returns subscription which is emitting Msg Tick every second. Every time application’s Model changes, Elm calls your implementation of function subscriptions with new Model (State in our terms) and receives new Sub. How to implement such behaviour in Kotlin? Turns out it’s not that difficult, thanks again to RxJava. First, we need a base class for Subscription instance

getObservable function receives params as an input and decides, whether it’s changed or not, and returns a Pair which contains an observable itself and flag of whether it’s recreated or not.

Now, we need to add it to the Program. And also we are adding now sub() method to our client interface Component

Every time our State changes, we check our subscription, if it’s recreated, and if they are, we dispose old subscription, and subscribe to a new one.

Let’s take a look at a real world case, where subscriptions are very handy. In sample app I have a screen with list of favorite translated words, which are stored in SQLite. And it is also it’s possible to filter them by first letters.

You can look at full working source code here

When user opens the screen for the first time, presenter subscribes to the database table with all favorite words, cause we have empty filter. Every time user enters a letter into filter EditText, State of screen changes, and we resubscribe to filtered words.

Many common scenarios can be incorporated into Elm dataflow very easily with subscription pattern, like locations changes, background/foreground app state, connectivity state, preferences changes and so on and so on..

Elm was initially designed as a runtime with Functional Reactive Programming model. Subscription is a simplified version of concept called Signal’s, the main building block for implementing concurrent FRP, which was lately dropped in favor of Subscription. If you are interested in FRP, I highly recommend to read master thesis about Concurrent FRP by Evan Czaplicki, creator of Elm. It provides insightful look at the history of Reactive systems and frameworks, and shows motivation for creating Elm.

Clean Architecture

Recently Clean Architecture gained a lot of traction in Android community. I won’t go deep into it, as there are lot of great articles which doing great job explaining it.

If you are not familiar with Clean Architecture or it’s implementations in Android, I highly recommend Fernando Cejas’s great articles or Five’s blog post series

In Clean Architecture, Elm pattern sits in Presentation layer. Presenter takes the responsibility of interacting with View interface, implementing Component interface for processing Elm dataflow, converting user actions to Msg, keeping reusable functions, etc.

The original image is courtesy of Fernando Cejas

Presenter in conjunction with Elm pattern provides presentation logic of your app.

Besides presentation logic, your app has Business logic, which sits in the domain layer and implemented by Use Cases. If we treat UI as a sequence of pure functions, then Use Cases are definitely representing side-effects, cause Use Case usually get data from some external data sources and works in another, non-main thread. That means, that we must work with Use Cases only in call() function, which is intended to process side effects.

You can look at a full source code here

To summarize, Elm pattern fits very well into the Clean Architecture and gets great amount of Presenter’s responsibilities. Function update() contains Presentation(UI) Logic and function call() contains Business Logic.

Update 02.04.2019

If you liked the ideas of Elm Architecture and want to try it out in Android app, we release it as library RxElm. You can find it on github.

You are very welcome to try it out, file an issues, and submit PRs ;-)

Time Travel

Thanks to Elm’s state-machine like processing of UI events, implementing time travel becomes a pretty easy task. It can be broken down to 2 parts: recording state changes and travelling to concrete state itself. We will need one class, which will store event records and relay, through which we will emit travel commands, and provide it to every Program instances(in other words, to every screen of the app).

We will store each new event on every screen, associated with new state and a screen object. The recording operation will follow right after update() function in our Program. The adventureMode flag is necessary for signaling Program that we are currently in time travel process, and hence, do not process incoming new Msg from UI.

Now, if we need our Component to react to travel commands, Program class needs to subscribe TimeTraveller’s relay, and for every emitted record call Component.

The whole travel process will be as follows —

  1. Set TimeTravel adventure mode to true.
  2. Send to TimeTravel’s relay start event and reset current state of the whole app to initial(in our case it’s MainActivity class).
  3. Then iterate through saved TimeRecords, and send them to TimeTravel’s relay.
  4. Implement travel() methods in our concrete components.

And that’s it, we have working TimeTravel debugger!

Conclusion

In the last fourth part I will go deep into how Elm pattern helps in testing complex UI scenarios, and will talk about state restoration after rotate or process kill and handling one time events.

Thanks for reading, I hope you enjoyed it!

If you want to discuss, follow to Reddit post

and follow me on twitter to get updates about my new posts

--

--