Coroutines with Lifecycle and LiveData

In this tutorial, you’ll build an Android app that uses coroutines with LiveData objects and lifecycle-aware CoroutineScopes. By Husayn Hakeem.

Leave a rating/review
Download materials
Save for later

Android architecture components provide first-class support for Kotlin coroutines. They let you write asynchronous code defined within CoroutineScopes tied to lifecycles and LiveData.

Because an Android app runs on its UI thread by default, it’s constrained to the lifecycle of its components. To move work off the UI thread, you need to write asynchronous code that runs within the lifecycle’s bounds. Otherwise, you could face memory leaks, power inefficiency and battery over-consumption.

In this tutorial, you’ll build a trading app with a user profile screen. This screen simulates displaying both static and real-time user information. A real app would fetch this data from a server, but in this tutorial, you’ll use dummy data.

By building this app, you’ll learn about:

  • Coroutines with LiveData builder.
  • Lifecycle-aware CoroutineScopes: ViewModelScope and LifecycleScope.
Note: This tutorial assumes you have previous experience using both Android Architecture Components and Kotlin Coroutines. If you’re new to Android Architecture Components, check out our Android Architecture Components: Getting Started tutorial, especially the sections on ViewModels and LiveData. Also, if you’re unfamiliar with Kotlin Coroutines, take a look at Kotlin Coroutines Tutorial for Android: Getting Started.

Getting Started

Click the Download Materials button at the top or bottom of the page to access the starter and final projects for this tutorial.

Next, open the starter project in Android Studio.

Take a moment to familiarize yourself with the code. You’ll see the following classes.

  • MainActivity.kt: Activity that wraps the user profile screen.
  • ProfileFragment.kt: Fragment that displays the user information.
  • ProfileViewModel.kt: ViewModel that provides the user information through LiveData using use case classes.
  • ProfileViewModelFactory.kt: ViewModelFactory that constructs ProfileViewModel instances.
  • GetUserInformationUseCase.kt: Use case that provides the user’s name, account number and phone number.
  • GetTotalValueUseCase.kt: Use case that provides the user’s total amount of money in real-time.
  • GetStocksUseCase.kt: Use case that provides the stocks the user is currently investing in.
  • GetRecommendedStockUseCase.kt: Use case that provides the user with a stock recommendation. It also provides a method to refresh it.

Build and run the app. You’ll see the profile screen, but the information fields are all empty. You’ll display the user’s information in no time!

Coroutine with Lifecycle and LiveData

Using Coroutines With LiveData

Generally speaking, when using LiveData, you might need to perform an asynchronous computation before returning a value, or many values. For this scenario, you’ll use the LiveData builder. It bridges the gap between LiveData and coroutines, where the asynchronous computation is wrapped in a suspend function and starts running once the LiveData instance becomes active.

fun <T> liveData(
  // 1
  context: CoroutineContext,
  // 2
  timeoutInMs: Long,

  // 3
  block: suspend LiveDataScope<T>.() -> Unit): LiveData<T>

The LiveData builder has three parameters:

When the block cancels because the LiveData becomes inactive, and if or when it becomes active again, the block is re-executed from the beginning. If you need to continue the operations based on where it last stopped, you can call LiveDataScope.lastValue to get the last emitted value.

  1. context: A CoroutineContext that defines the context for running the block. It defaults to the MainCoroutineDispatcher.
  2. timeoutInMs: The duration in milliseconds after which the block is cancelled if there are no active observers on the LiveData. It defaults to five seconds.
  3. block: A suspend function that runs an asynchronous operation when the LiveData is active. If the block completes successfully or is cancelled due to reasons other than the LiveData becoming inactive, it won’t re-execute even after the LiveData goes through the active/inactive cycle.

Adding the LiveData Builder Dependency

To use the LiveData builder in your trading app, add the following dependency to the project’s app/build.gradle.

implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"

Emitting Single Values Through LiveData With Coroutines

Using the LiveData builder, you can emit single values which are caught by the LiveData’s active observers. To do this, use LiveDataScope.emit().

val aLiveData: LiveData<String> = liveData {
  // 1
  val result: String = computeResult()

  // 2

Here’s what’s going on with the code above:

  1. computeResult() can be a suspend function that performs an asynchronous operation and returns a String. For example, it can fetch a value from a server over the network, or read a value from a local database.
  2. Once the result is ready, you emit it using the emit() function. This calls LiveData.setValue() internally.

Displaying User Information

Ideally, your user could view and edit their personal information on the profile screen, but that’s out of the scope of this tutorial. Instead, you’ll focus on displaying the user information.

Open GetUserInformationUseCase.kt. To return the user’s information using LiveData builder, implement get() by adding this:

fun get(): LiveData<UserInformation> = liveData {
  // 1

  // 2

In the function above, you:

  1. Pause the execution of the function for the duration of GET_USER_INFORMATION_DELAY. This simulates fetching the data asynchronously.
  2. Emit a UserInformation instance with dummy data. Any active observers of this LiveData get this result.

Build and run the app. The screen now displays the user’s name, account number and phone number.

Coroutines and LiveData: Emitting Single Values

Next, you’ll show the user’s total investment value in real-time!

Emitting Values From a LiveData Source With Coroutines

LiveData builder doesn’t limit you to just emitting a single value. It also allows you to emit a different source from which data can be returned using LiveDataScope.emitSource(). Whenever its value changes, the LiveData builder emits that same value. Generally speaking, this looks as follows:

val aLiveData: LiveData<String> = liveData {
  // 1

  // 2

  // 3

val source1: LiveData<String> = ...
val source2: LiveData<String> = ...

The block above:

  1. Sets source1 as a source for the LiveDara builder. Whenever source1 has a new value, aLiveData receives and emits it.
  2. Emits a single value to the LiveData’s active observers. It also removes source1 as a source. This means even when source1‘s value changes, aLiveData no longer emits that new value.
  3. Sets source2 as a source for the LiveData builder. aLiveData now listens to changes in source2. Whenever its value updates, aLiveData emits the new value.

You’ll use this technique to update your user’s total investments in real-time!