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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Coroutines with Lifecycle and LiveData
20 mins
- Getting Started
- Using Coroutines With LiveData
- Adding the LiveData Builder Dependency
- Emitting Single Values Through LiveData With Coroutines
- Displaying User Information
- Emitting Values From a LiveData Source With Coroutines
- Displaying the User’s Total Investment Amount
- Transforming LiveData With Coroutines
- Displaying the User’s Investments
- Using ViewModelScope
- Adding the ViewModelScope Dependency
- Displaying a Stock Recommendation
- Refreshing the Stock Recommendation
- Using Lifecycle-Aware Coroutine Scopes
- Adding the LifecycleScope Dependency
- Displaying a Dialog on the Screen’s First Launch
- Where to Go From Here?
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.
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!
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.
-
context: A CoroutineContext that defines the context for running the
block
. It defaults to the MainCoroutineDispatcher. -
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. -
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.When the
block
cancels because the LiveData becomes inactive, and if or when it becomes active again, theblock
is re-executed from the beginning. If you need to continue the operations based on where it last stopped, you can callLiveDataScope.lastValue
to get the last emitted value.
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
emit(result)
}
Here’s what’s going on with the code above:
-
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. - Once the result is ready, you emit it using the
emit()
function. This callsLiveData.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
delay(GET_USER_INFORMATION_DELAY)
// 2
emit(UserInformation(USER_NAME, ACCOUNT_NUMBER, PHONE_NUMBER))
}
In the function above, you:
- Pause the execution of the function for the duration of
GET_USER_INFORMATION_DELAY
. This simulates fetching the data asynchronously. - 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.
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
emitSource(source1)
// 2
emit("value")
// 3
emit(source2)
}
val source1: LiveData<String> = ...
val source2: LiveData<String> = ...
The block above:
- Sets
source1
as a source for the LiveDara builder. Wheneversource1
has a new value,aLiveData
receives and emits it. - Emits a single value to the LiveData’s active observers. It also removes
source1
as a source. This means even whensource1
‘s value changes,aLiveData
no longer emits that new value. - Sets
source2
as a source for the LiveData builder.aLiveData
now listens to changes insource2
. Whenever its value updates,aLiveData
emits the new value.
You’ll use this technique to update your user’s total investments in real-time!