Create a Clean-Code App with Kotlin Coroutines and Android Architecture Components — Part 3

The View

Marek Langiewicz
EL Passion Blog
9 min readNov 23, 2017

--

This is the third blog post about using Kotlin Coroutines and Android Architecture Components in one simple weather app.

Please read the first and second part if you haven’t yet

We already have a working app implemented as the MainModel class. Now we need to create the UI for it. This part will be android specific.

Unfortunately, the MainModel is already a bit android specific because it uses LiveData (and ViewModel) from Android Architecture Components

06 View

The MainActivity class represents a View that observes our Model.

https://github.com/elpassion/crweather/…../MainActivity.kt

Thanks to Android Architecture Components, our activity is really small. We don’t need to override any lifecycle methods like onStart, onResume, onPause, onStop, etc… We only need onCreate to set things up.

In onCreate method we set up content view, action bar, optionally the drawer, and the recycler view’s adapter. Just usual android stuff. Beside that we do two more things:

  • We set up a click listener in navigation view with cities, so it invokes the SelectCity action on our model (line 25)
  • We initialize the model itself (line 28)

Let’s take a closer look at the model initialization:

First line (32) gets the MainModel instance from ViewModelProvider which in turn is provided by ViewModelProviders.of(Activity) factory method. Here the Android Architecture Components library works a bit like Dagger. It manages any ViewModel instances for us, and makes sure we get the same model instance after screen rotation (or any other device configuration change). This way, the new MainActivity instance can access the same model instance, “subscribe to” (or rather “observe”) the same app state LiveData objects, and display appropriate UI whenever the state changes.

There is a nice detailed description of this “dependency injection” mechanism at the beginning of ViewModel class doc.

Now, when we have our model instance, we can observe it. The LiveData.observe method takes the LifecycleOwner as a first parameter, so the LiveData knows when our activity is ready to receive state updates, and when it is destroyed. The LiveData class has a few guarantees that simplify the way we observe it:

  • All state changes are dispatched on the main thread. This means we can immediately access android views and update displayed data.
  • Changes are dispatched only when the observer is active: its Lifecycle is in a STARTED or RESUMED state. This means we won’t get any unnecessary updates when our activity is not visible.
  • New observer receives current value as soon as it becomes active. This means we will always have something to display as soon as our activity becomes visible.
  • When the observer is DESTROYED the LiveData removes it from the observer list. This means we don’t have to remember to “unsubscribe” because it will be done automatically.
  • The order of all these life cycle related events is carefully designed so we never get infamous fragment transaction exceptions. Watch a few minutes of this youtube presentation (from 16:30) if you want more details.

Here, in the MainActivity, we just set up LiveData observers to call our display… functions:

  • displayLoading Shows/hides the progress bar.
  • displayCity Shows given city as selected in navigation view.
  • displayCharts Shows given list of charts in recycler view (with some animations) — we will analyze it later.
  • displayMessage Shows given (usually error) message (using a toast for simplicity).

Unfortunately, LiveData (as well as ViewModel) is android specific, so it complicates testing. However, we could achieve better separation of concerns by implementing this “lifecycle aware” feature of LiveData with RxJava. It would require: some special Observable<LifecycleEvent> to signal lifecycle changes; some BehaviorSubject (to hold data and to return last known value on subscribe); switchMap operator (to wait until observer is active); takeUntil operator (to automatically unsubscribe at appropriate LifecycleEvent); and maybe more. We could also use a library, like RxLifecycle. In El Passion we’ve already tried this solution with RxLifecycle in one hackathon project (Teamcity Android Client), and it worked great.

I guess, implementing all guarantees that LiveData gives us (like: no fragment transaction exceptions), could be tricky.

To display the chart list we use a regular recycler view with very basic adapter setup. The interesting part is how we present a single item (Chart). We implement custom view class: ChartView with base class: CrView. These two classes use suspendable functions and actors to implement chart animations. Let’s take a detailed look at it.

07 CrView

Redrawing UI in Android (as in other UI frameworks) is asynchronous. We call View.invalidate() to let the system know we want to redraw this view, then the system decides exactly when to call our View.onDraw(Canvas) function to actually redraw the view. But what if we could pretend it is synchronous? We could then use ordinary control flow constructs like loops and just redraw the view for each animation frame in a loop (with some delay in between which would also pretend to be synchronous). We already know how to implement such (pretending to be synchronous) functions: suspending functions.

https://github.com/elpassion/crweather/…../CrView.kt

The first thing we do here (line 8, 11, 28) is to allow user to set some code block to be executed on Canvas every time the system calls onDraw.

Next, we create the suspend fun redraw(). The implementation is actually pretty simple. All we want to do is to let the system know that we want to be redrawn (line 17), then suspend and save given continuation somewhere (line 18). Later, when the system calls onDraw, we check if we have some redraw call suspended and resume it if that is the case (line 29). We resume asynchronously so onDraw always ends fast.

That’s basically it. I’ve also added second suspendable function: draw, to show another possible way to use this class, but I don’t actually use it now.

Now let’s use CrView as a base class to implement a custom view that can display and animate weather charts.

08 ChartView

https://github.com/elpassion/crweather/…../ChartView.kt

We want to have public mutable property var chart: Chart with a custom setter that will cause new chart to appear (but not immediately — it should smoothly morph from current chart to the new one). Computations needed for animation and the drawing itself is handled by an actor, so the chart setter only sends all new charts to the actor’s mailbox. We use the non-suspendable operation: offer (line 11) to send new charts to the actor. This will override any unprocessed chart in actor’s mailbox with the new one.

Now, let’s take a look at the actor implementation.

09 Second Actor

We’ve already seen one actor in my previous blog post:

This one is also implemented with the actor coroutine builder, it uses the same coroutine dispatcher: UI, and the same channel type: Channel.CONFLATED.

The idea is to keep the currentChart in a local variable (and the currentVelocities which represents speed and direction of every chart point animation); then to use an ordinary while loop in which we move the currentChart points just a little bit towards their final positions (destinationChart), redraw the view, then delay for a few millis, and repeat. This while loop takes care of animating the currentChart towards the destinationChart required by the user. This animation loop has to be placed inside another for loop which iterates through actor’s mailbox and retrieves next destinationCharts offered by the user.

We have three suspension points here:

  • Receiving the next chart from the mailbox (line 21).
  • Redrawing the view inside the inner while loop for every animation frame (line 30).
  • Suspending for 16 millis after every animation frame (line 31).

I’ll skip the most boring implementation details (you can inspect it all on the GitHub/CrWeather), except a few:

  • At the beginning (line 19) we set up the CrView so that it draws currentChart every time the system wants to redraw the view.
  • The outer for loop iteration (line 21) almost never suspends because the inner while loop finishes only when there is already a new chart in the mailbox.
  • We use the same data class: Chart to remember velocities of currentChart points — every Point here represent a velocity vector. I’ve reused the Chart class just to keep the code short — it would be better to have a separate data structure for chart points velocities.
  • After getting a new destination chart, we copyAndReformat the currentChart (line 23) so it contains as many points as the new destinationChart.
  • The same goes with currentVelocities (line 25)
  • The moveABitTo extension function which actually moves the currentChart towards destinationChart, also changes velocities a bit, so the animation stabilizes after a while. Check the ChartsUtils.kt file for the implementation details.

But what happens if the system abandons the particular ChartView instance? Will the actor compute new animation frames indefinitely?
It won’t, and here is why:

The redraw function calls the View.invalidate() and suspends, but the system will not call onDraw on this View object anymore. This means our Continuation will not be resumed at all. So, if the system has no live reference to the ChartView, then the reference to our suspendable point (the Continuation) is also lost, so it all can be garbage collected together with the actor itself. In general, when a suspended coroutine is forgotten (so no one keeps a reference to the Continuation), it will be correctly garbage collected.

Thanks to Kotlin Coroutines we can express the logic of animations and the logic of receiving new “morphing goals” in very natural way. The code shows how we think of this problem — we think: “for every new chart… while(no new chart): morph the chart a bit, redraw current chart, wait for a little, repeat”, and this is exactly how the implementation looks like. It’s also great that we can keep more state as local variables (like: currentChart and currentVelocities) under full control of specific actor — which (as every coroutine) runs sequentially.

Notice that we mutate state from within the actor code that is accessed outside by CrView.onDraw. In general, the actor model of concurrent computation forbids any shared state in favour of implementing all communication via actor’s mailboxes.

It would also be very easy and natural to extend this actor logic, compose new animation phases, etc. We can easily implement small suspendable functions (with ordinary conditional statements, loops, etc) and just call those functions inside the actor‘s code.

I think this style is simpler and a lot more composable than traditional object-oriented design — compare it to all those classes in Android Property Animation system. Although all those ValueAnimators, ObjectAnimators, AnimatorSets, different Evaluators and Interpolators, allow to create very sophisticated animations; implementing a similar system with suspendable functions would be more natural and would not require nearly as much boilerplate code.

10 Conclusions

The Kotlin Coroutines can successfully compete with RxJava when it comes to fighting the Callback Hell, but on the other hand, it can cooperate with RxJava very well too.

In my opinion, we shouldn’t think of a suspendable function as just a function with some additional feature. This concept has a potential to greatly change the way we create our software in this more and more asynchronous world.

The second technology we’ve tested here — Android Architecture Components — is not such a big deal as the Kotlin Coroutines but it’s still quite useful. The LiveData class helps if you want some (easy to use) observable streams (and if you don’t want to include RxJava); and the ViewModel class helps if you want a simple “dependency injection” (and if you want to stop worrying about activity lifecycles and device configuration changes).

Software development on Android is becoming more and more exciting thanks to amazing libraries, tools, and (most importantly) Kotlin :-)
If you’re wondering about the future, I think Svetlana Isakova from JetBrains has the right answer for you (in two sentences): The Future of Kotlin :-)

Also check out Andrey Breslav post: Kotlin Future Features Survey Results

Tap the 👏 button if you found this article useful!

About the Author
Marek is an Android Developer at EL Passion. You can find him on GitHub or at his website.

Find EL Passion on Facebook, Twitter and Instagram.

--

--