Android + Coroutines = ❤️ in 2021?
However, since Kotlin Coroutines API was announced stable, it’s considered a more lightweight and exciting alternative to Rx more and more often.
Google officially admits it is all hands for using coroutines in production, as they help to scale execution of async jobs on Android OS efficiently. But still, at the same time, Google promotes using LiveData with ViewModel as the state-of-the-art pattern for Android app architecture and maintaining objects which can survive screen configuration changes (e.g. screen rotation). Having three different APIs: LiveData, Coroutines, and Flow can make you feel a bit overwhelmed. Luckily, as explained at Android Dev Summit 2019, useful extensions will be added to the Jetpack’s lifecycle-viewmodel-ktx and lifecycle-livedata-ktx packages supposed to help with integrating those APIs seamlessly. We have just tested them out in action and here are the five cool examples of what you can do with them.
1. Autocancelation with lifecycleScope and viewModelScope
Having the ViewModel class whose instances can survive screen configuration changes makes it necessary to handle coroutines cancelation the smart way. Obviously, we don’t want to call a coroutine’s cancel() function manually when ViewModel.onCleared() is invoked. It’s not smart enough. Instead, we want the coroutine to cancel automatically when the view model is no longer going to exist. This is the case where the extensions like viewModelScope and lifecycleScope can help.
The viewModelScope is an extension property of the ViewModel class which provides a CoroutineScope instance bounded to the life scope of the view model instance. In order to start the auto-cancellable coroutine within that scope just use the launch() or async() function as follows:
viewModelScope.launch(Dispatchers.IO) { doSomeLongRunningJob() }
Similarly, the lifecycleScope is an extension property of the LifecycleOwner instance (e.g. Activity or Fragment), and it’s bounded to the Lifecycle of the Activity or Fragment.
2. Associating coroutine launch time with Lifecycle state
The main advantage of the lifecycleScope CoroutineScope is its built-in cancelation mechanism, which cancels the coroutine on the lifecycle's Destroy event.
lifecycleScope.launch { doSomeLongRunningJob() }
In addition to auto-canceling, LifecycleCoroutineScope also provides three more functions, which are helpful when it comes to scheduling coroutine start time. Apart from the basic LifecycleCoroutineScope.launch(), which executes the coroutine immediately, the following functions are available:
- LifecycleCoroutineScope.launchWhenCreated()
It launches when the Lifecycle controlling this LifecycleCoroutine scope is at least in the Lifecycle.State.CREATED state.
- LifecycleCoroutineScope.launchWhenStarted()
It launches when the Lifecycle controlling this LifecycleCoroutine scope is at least in the Lifecycle.State.STARTED state.
- LifecycleCoroutineScope.launchWhenResumed()
It launches when the Lifecycle controlling this LifecycleCoroutine scope is at least in the Lifecycle.State.RESUMED state.
For example, the code snippet below will launch the coroutine when the Lifecycle of an Activity is in the Lifecycle.State.RESUMED state. It will run until the coroutine block completes or the Lifecycle is destroyed.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launchWhenResumed {
doSomeLongRunningJob()
}
}
}
3. Creating LiveData instance emitting values inside a Coroutine block
LiveData is often used to expose emitted values from view models to the views. In case we’d like to start an asynchronous coroutine job that emits some State values to the LiveData, we can use the code like this:
val myLiveData: LiveData<State> = MutableLiveData<State>().apply {
viewModelScope.launch {
this@apply.postValue(State.Loading)
val result = longRunningTask()
this@apply.postValue(result)
}
}
And with the liveData builder we can simplify it to the following lines:
val myLiveData: LiveData<State> = liveData<State> {
emit(State.Loading)
emit(longRunningTask())
}
It’s also worth noting the liveData function can take one of the Dispatchers as an argument to dispatch the job on it and timeoutInMs - the timeout in ms before canceling the block if there are no active observers.
4. Easy Flow to LiveData converting
Are you working with Kotlin Flow API and want to convert it to LiveData instance? Now you can do it with the Flow.asLiveData() extension function. That’s it!
5. Representing callback-style API as a Coroutine suspend function
Coroutines are great when it comes to async programming because they allow writing non-blocking code in a natural way. However, sometimes we are forced to work with the old school callback-style async code. That’s often a case when we are dealing with old Java SDKs and APIs. So how can we effectively merge the callbacks with the coroutine’s code? This is the case where the uspendCancellableCoroutine() function from Kotlin’s standard library can help! Let’s take a look at how we can use it in action.
suspend fun inteactWithAPI(param: String) : Result<String> =
suspendCancellableCoroutine { continuation ->
api.addOnCompleteListener { result ->
continuation.resume(result)
}.addOnFailureListener { error ->
continuation.resumeWithException(error)
}.fetchSomethingFromAPI(param)
}
The lambda block passed to the suspendCancellableCoroutine function exposes an instance of CancellableContinuation, which you can access in order to emit the result or exception within the coroutine. To cut the long story short, it allows converting callbacks into coroutine suspend function syntax.
Summary
Still unsure whether you should give Coroutines a try in your app? You should not hesitate anymore!
After the Coroutines framework was announced stable, Google decided to promote it as a first-class choice for async programming on Android. And the new Lifecycle and ViewModel ktx extensions are definitely going to bring more joy to async programming with coroutines.
You can watch the full talk from Android Dev Summit 2019 on Google's vision of using Android’s LiveData along with Coroutines and Flow. Would you like to see some code instead? Here you are.