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
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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:

  1. Emit a value to display while the total amount computes asynchronously.
  2. Pause for one second to simulate the asynchronous changes of the amount.
  3. Set getTotalValue() as a source for this LiveData builder.
  4. As long as the LiveData has an active observer, this loop keeps running and updating the total amount. This simulates real-time updates.
  5. 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.
  6. Update the total amount using the extension function moreOrLessWithMargin() and then emit it. You notify the get() 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!

Coroutines and LiveData: Emitting Values From a Source

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.

Coroutines and LiveData: Transformations

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:

  1. Use the IO dispatcher as the function’s coroutine context, since fetching the recommendation would probably involve a network call.
  2. Randomly return any of the stocks in RECOMMENDED_STOCKS.
Note: Use 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!

ViewModelScope

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.

ViewModelScope

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!