Exploring new Coroutines and Lifecycle Architectural Components integration on Android

Paolo Rotolo
May 23 · 7 min read

oogle I/O brought various interesting things for Android Developers: there’s the cutting edge JetPack Compose, the new F̶l̶u̶t̶t̶e̶r̶ ̶i̶n̶ ̶K̶o̶t̶l̶i̶n̶ declarative toolkit to build UI using composable Widgets, but it’s still in early early stage of development and it’s not intended for production use in the short term.

Something that’s actually more interesting for Kotlin Developers, is the new integration between Coroutines and Arch Components, lifecycle and liveData in primis and by now I think you’ve already guessed what is the topic we’re going to cover in this article :)


First of all, to get all the new features, you need to use version androidx.lifecycle:*:2.2.0-alpha01 or more of the lifecycle libraries.

But that’s not all. If you look at the docs you’ll find out there’s a new Coroutine scope called lifecycleScope and a new LiveData building block.

Including that, is actually quite tricky… Searching online it’s not very hard to find comments like this:

…or this…

There’s in fact another library that Google silently released that enables those features called lifecycle-runtime-ktx that you need to import as well.

So, the full list of imports required to try all the features listed in this post is:

def arch_version = '2.2.0-alpha01'implementation "androidx.lifecycle:lifecycle-extensions:$arch_version"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$arch_version"implementation "androidx.lifecycle:lifecycle-livedata-ktx:$arch_version"implementation "androidx.lifecycle:lifecycle-runtime-ktx:$arch_version"

I’m assuming you’re already familiar with Coroutines basics on Android. If you’re not, check out this awesome series of Medium posts by Sean McQuillan.

What’s new in androidx.lifecycle:*:2.2.0-alpha01

Coroutine Scope and ViewModelScope

You probably know that Coroutines run in a scope that will handle for you useful things like get uncaught exceptions and cancellation to make sure you never leak a Coroutine.

If you need to call a suspending function from a ViewModel, instead of manually creating a new scope, there’s one already ready for you: viewModelScope is bound to ViewModel’s lifecycle and will handle for you cancellation when the ViewModel onClear() is called. You can easily access this scope from an extension function available on viewmodel-ktx library.

Here you can see an example of a function to sign in the user using Firebase Auth.

Note that Firebase Auth’s method signInWithEmailAndPassword returns a Task that you can conveniently convert to a cancellable suspending function using await() from kotlinx-coroutines-play-services.

LifecycleScope

Starting from lifecycle-runtime-ktx:2.2.0-alpha1, there is also a similar scope for all Lifecycle objects like Activities and Fragments. Again, all the coroutines in that scope will be canceled when the Lifecycle is destroyed.

So it would be:

activity.lifecycleScope.launch {
 // scope bound to Activity Lifecycle
}fragment.lifecycleScope.launch {
 // scope bound to Fragment Lifecycle 
}fragment.viewLifecycleOwner.launch{
 // scope bound to Fragment View
}

Be aware that lifecycleScope is convenient when dealing with UI events like, for example, showing a tip for the user and hiding it after a small delay.

lifecycleScope.launch {
    delay(5000)
    showTip()
    delay(5000)
    hideTip()
}

Without using Coroutines and lifecycleScope, this would be:

val DELAY = 5000Handler().postDelayed({
  showTip()
    Handler().postDelayed({
      hideTip()
    }, DELAY)
}, DELAY)

You should not use lifecycleScope for other tasks like retrieving data from repositories or making long computations. This is because those scopes are bound to Activities and Fragments lifecycles and will cancel the coroutines inside them when, for instance, there’s a configuration change.

In short, you will end up with your network request canceled if the user rotates the phone, defeating the whole purpose of a ViewModel behind an Activity. That’s why it’s better to call those methods inside the viewModelScope and let the activity only observe the LiveData value. We’ll come back to this later.

But that’s not all. You may want to run those coroutines only if your Lifecycle is in a certain state.

Let’s refresh our memory with all possible lifecycle states from Android docs:

Again, reading from the docs, you’ll notice that:

When a Fragmentstate is saved via onSaveInstanceState(), it's UI is considered immutable until ON_START is called. Trying to modify the UI after the state is saved is likely to cause inconsistencies in the navigation state of your application which is why FragmentManager throws an exception if the app runs aFragmentTransaction after state is saved.

So if you need to run a FragmentTransaction inside your coroutine scope, you need your Fragment lifecycle to be at least STARTED.

You can achieve this using lifecycleScope.launchWhenStarted to ensure your code will be called at the right time:

LiveData

One of the big news introduced with livedata-2.1.0-alpha1, is interoperability between LiveData and Coroutines.

Let’s start with an example. You want to make a network request and expose the result to an Activity with a LiveData. The activity will start observing the LiveData which will be empty at first and will be notified when data is available from the network call.

Starting from 2.2.0-alpha01, LiveData has a new building block that will automagically execute when the LiveData becomes active. No need to add that you’ll observe LiveData using a LifecycleOwner, so it will know when to stop executing and cancel the coroutines inside the building block. That’s very similar to lifecycleScope we’ve seen earlier.

Inside the LiveData building block, you can use emit() to set a value to the LiveData. Since LiveData allows only setting values from the main thread, emit() is always executed on this one.

So, the previous example will now become:

This is a lot more simple and concise. Also note that we’re making a network request, so it would be better to run our suspending function on the IO Dispatcher. We can do that passing the dispatcher we want to use as parameter of the liveData block:

Simple as that. No need to switch dispatcher to Dispatchers.Main for the emit() function, since it will always automatically run on the main thread.

By the way, you might want to know that Retrofit now supports suspending functions. Well, retrofit:2.5.1-SNAPSHOT does, so if you’re more into stable releases, check out Jake Wharton’s Kotlin Coroutine Adapter.

Let’s see another example. Now you want to load first a cached copy of some data and then start getting the updated values, for example, from Firebase Database.

The flow would be:

  1. Emit cached copy (Data)
  2. Emit Firebase Database data (LiveData<Data>)

Since Firebase already returns a LiveData, you’ll end with another LiveData in your ViewModel: the one from Firebase, and the one you’re building which has just emitted the cached data.

With emitSource() you can not only emit a single value, but attach your LiveData to another LiveData and start emitting from it. Anyway, each emit() or emitSource() call will remove the previously added source.

The activity that’s observing the someData object, will quickly receive the cached data on the device and update the UI. Then, the LiveData itself will take care of making the network request and replace the cached data with a new live stream of data, that will eventually trigger the Activity observer and update the UI with the updated info.

A similar example would be loading local data, fetching new data from a server and update cached data with the new one.

If you’re using Room, you probably know you can get LiveData out from a query:

In this case, you’re feeding the post LiveData with Room’s data. When an updated version of the post will be available, it will update the copy in the local database and Room will automatically emit the value.

We said early that LiveData, after a short timeout, will cancel all the coroutines that are still running when the attached lifecycle of the observer is destroyed, so no need to worry for that. You can adjust that timeout passing a Long as parameter with the milliseconds to wait before cancellation.

var data = liveData(timeoutInMs = 10000) {
    val data = dataRepository.getData()
    emit(data)
}

Recap

So now you have some new Coroutine Scopes, similar to viewModelScope, the lifecycleScopes bound to Activities, Fragments, View… in short every object that has a LifecycleOwner attached. You can use those to write UI related coroutines and eventually specify in which state of lifecycle run them.

You also have a new LiveData building block that comes with a Scope that lets you run coroutines and emit data from inside. Those coroutines will cancel after a small delay when the lifecycle of the object that is observing livedata is destroyed.


But await(), there’s more!

If you want to know more about Kotlin Coroutines with other Architectural Components here’s the official documentation on Android Developers. There’s also a good I/O session about the same topic available on YouTube.

Happy coding, the Corouteam!

Corouteam

A team of curious developers from GDG Bari

Paolo Rotolo

Written by

Making the world a better place. Android Developer @Nextome. GDG Bari co-lead, Engineering student, OpenSource and community lover.

Corouteam

Corouteam

A team of curious developers from GDG Bari