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?
Displaying the User’s Total Investment Amount
During trading hours, stock prices gain and lose value. Since stock prices often fluctuate, it would be helpful to display the user’s total investment amount of money in real-time.
In a real app, you might implement this using webhooks. For this tutorial, you’ll simulate this fluctuation using a random change in prices.
Open GetTotalValueUseCase.kt. Replace the get()
to return the total investment value for display using the following code:
fun get(): LiveData<Double> = liveData {
// 1
emit(0.00)
// 2
delay(TOTAL_VALUE_INITIAL_DELAY_MS)
// 3
emitSource(getTotalValue())
}
private fun getTotalValue(): LiveData<Double> = liveData {
var total = INITIAL_TOTAL_VALUE
// 4
while (true) {
// 5
delay(TOTAL_VALUE_UPDATE_RATE_MS)
// 6
total = total.moreOrLessWithMargin(TOTAL_VALUE_DIFF_RANGE)
emit(total)
}
}
In the two functions above, you:
- Emit a value to display while the total amount computes asynchronously.
- Pause for one second to simulate the asynchronous changes of the amount.
- Set
getTotalValue()
as a source for this LiveData builder. - As long as the LiveData has an active observer, this loop keeps running and updating the total amount. This simulates real-time updates.
- Pause for two seconds before each update to the total amount. This lets you simulate the initial time it takes to fetch the result, perhaps from a server, and also sets the rate at which the total investment amount updates.
- Update the total amount using the extension function
moreOrLessWithMargin()
and then emit it. You notify theget()
function observing this LiveData of this change, and also emit this newly computed value.
Build and run the app. You’ll see the total amount now displays on the screen, and updates every two seconds!
Now the app is coming along! Next, you’ll display the list of stocks in which your user is currently investing.
Transforming LiveData With Coroutines
Just like you can apply a transformation to LiveData using Transformations.switchMap()
, you can do the same with the LiveData builder by using liveData.switchMap(transform)
, which applies transform()
on each element it receives. Generally speaking, the code might looks like this:
val aLiveData = LiveData<String> = ...
val transformedLiveData: LiveData<Int> = aLiveData.switchMap { element ->
liveData {
val transformedElement = transform(element)
emit(transformedElement)
}
}
Whenever aLiveData
‘s value changes, this new value is received inside the switchMap()
function. It’s transformed using transform()
and then emitted to its active observers.
You’ll use this approach to show your user’s investments.
Displaying the User’s Investments
In addition to the total amount of their investments, the user might want to see all the stocks in which they’re investing.
Open GetStocksUseCase.kt and add the following method:
private fun getStocks(): LiveData<List<String>> = liveData {
delay(GET_STOCKS_DELAY)
emit(STOCKS)
}
Using a LiveData builder, this above code gets a list of stocks in which the user is currently investing. This operation is asynchronous to simulate fetching the information from somewhere else, such as a server.
Next, in order to transform this list into a String that’s displayable on the screen, you’ll use a switchMap()
transformation. Replace the get
function in GetStocksUseCase
with the following:
fun get(): LiveData<String> = getStocks().switchMap { stocks ->
liveData {
emit(stocks.joinToString())
}
}
The get()
function observes getStocks()
. Once it returns a value, it transforms it and emits a new value.
If getStocks()
returns [“stock1”, “stock2”, “stock3”], then get()
transforms this list by joining its elements, returning the String “stock1, stock2, stock3”.
Build and run the app. You’ll see the list of the user’s stocks.
You’re almost there! The last piece of information missing from the user profile screen is the user-curated stock recommendation. Before you add that feature, you’ll first need to explore ViewModelScopes
.
Using ViewModelScope
Use the ViewModelScope when you’re performing an operation inside a ViewModel and need to make sure it cancels when the ViewModel isn’t active. It’s defined for every ViewModel and ensures any coroutine launched within it cancels when the ViewModel clears.
Generally speaking, to use the ViewModelScope, access it through the viewModelScope attribute inside a ViewModel. The code to do this might looks as follows:
class MyViewModel: ViewModel() {
fun aFunction() {
viewModelScope.launch {
performOperation()
}
}
}
performOperation()
runs inside a coroutine that automatically cancels when the ViewModel clears. You’ll use this technique to fetch stock recommendations in your app.
Adding the ViewModelScope Dependency
To use ViewModelScope, add the following dependency to the project’s app/build.gradle:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
This will add the necessary KTX extensions to the ViewModel.
Displaying a Stock Recommendation
You can help your user make better investment choices, especially if they’re a novice stock investor, by recommending a stock for them to invest in.
Open GetRecommendedStockUseCase.kt and replace the contents of get()
with the following code:
// 1
suspend fun get() = withContext(ioDispatcher) {
delay(GET_RECOMMENDED_STOCK_DELAY)
// 2
_recommendedStock.postValue(RECOMMENDED_STOCKS.random())
}
In this code, you return a stock recommendation for display after a delay. The code will:
- Use the IO dispatcher as the function’s coroutine context, since fetching the recommendation would probably involve a network call.
- Randomly return any of the stocks in
RECOMMENDED_STOCKS
.
postValue()
to set the new value of the recommended stock instead of setValue()
since this operation takes place on a thread other than the main thread.
Finally, you’ll need to call GetRecommendedStockUseCase.get()
from ProfileViewModel
. Replace getRecommendedStock()
in ProfileViewModel
with the following:
private fun getRecommendedStock() {
viewModelScope.launch {
getRecommendedStockUseCase.get()
}
}
Since there’s no guarantee fetching the recommended stock will complete before the ViewModel clears, launch this operation from the ViewModelScope.
Build and run the app. Now you’ll see the recommended stock on the screen!
Click the refresh button next to the recommended stock. Notice nothing happens. You’ll fix that in a moment.
Refreshing the Stock Recommendation
The stock market is unpredictable. Since stock prices rise and fall the recommended stock may change with time. It may even change abruptly, which is why you need to let your user refresh it when they want.
Open GetRecommendedStockUseCase.kt and replace the contents of refresh()
with the following:
suspend fun refresh() = withContext(ioDispatcher) {
_recommendedStock.postValue(REFRESHING)
get()
}
In the code above, you first update the UI to let your user know the recommended stock is being refreshed because getting the refreshed value may take some time. Then you call get()
again to return a random stock.
Next, update refreshRecommendedStock()
in ProfileViewModel
to use viewModelScope to call the refresh()
method since it is now a suspending function:
fun refreshRecommendedStock() {
viewModelScope.launch {
getRecommendedStockUseCase.refresh()
}
}
Build and run the app. Click the refresh button. Notice the message “Refreshing…” before a new recommended stock shows.
The app looks great! The total investment amount updates in real-time, the user information is on the screen and the user can interact with the UI to update the stock recommendation.
How can it get better, you ask? Scroll to the next section and find out!