Kotlin Coroutines Tutorial for Android : Advanced

Gain a deeper understanding of Kotlin Coroutines in this Advanced tutorial for Android, by replacing common asynchronous programming methods, such as Thread, in an Android app. By Rod Biresch.

3.3 (4) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Updating the Repository

First, open Repository.kt and modify it as follows:

interface Repository : DefaultLifecycleObserver {
  fun getPhotos(): LiveData<List<String>?>
  fun getBanner(): LiveData<String?>

  fun registerLifecycle(lifecycle: Lifecycle)
}

Here, you extended from DefaultLifecycleObserver. This allows you to listen to lifecycle events and provide your custom logic.

The new registerLifecycle function will provide the ability to hook in the life-cycle from the PhotosFragment.

Updating Injection Singleton

Next, open Injection.kt. Change the method signature on provideViewModelFactory() to the following:

fun provideViewModelFactory(lifecycle: Lifecycle): PhotosViewModelFactory {
  val repository = provideRepository()
  repository.registerLifecycle(lifecycle)
  return PhotosViewModelFactory(repository)
}

You included a Lifecycle parameter. Then, register the Lifecycle in the Repository.

Updating the PhotosFragment

Now, open PhotosFragment.kt and change the following method:

override fun onAttach(context: Context) {
  super.onAttach(context)

  val viewModelFactory = Injection.provideViewModelFactory(lifecycle)
  viewModel = ViewModelProvider(this, viewModelFactory).get(PhotosViewModel::class.java)
}

You’ve provided the Fragment’s lifecycle as an argument in provideViewModelFactory().

Registering the Lifecycle

Next, open PhotosRepository.kt again, and implement the new registerLifecycle() function.

override fun registerLifecycle(lifecycle: Lifecycle) {
  lifecycle.addObserver(this)
}

This adds an observer that will be notified when the Fragment changes state.

Main-Safe Design

Google encourages main-safety when writing coroutines. The concept is similar to how the Android system creates a main thread when an app launches.

The main thread is in charge of dispatching events to the appropriate user interface widgets. You should delegate I/O and CPU-intensive operations to a background thread to avoid jank in the app.

Main-safety is a design pattern for Kotlin coroutines. It lets coroutines use the Dispatchers.Main, or main thread, as a default threading context. They then favor delegating to Dispatchers.IO for heavy I/O operations or Dispatchers.Default for CPU heavy operations.

A CoroutineScope interface is available to classes which require scoped coroutines. However, you must define a CoroutineContext instance which the scope will use for all the coroutines. Let’s do that.

Defining CoroutineScope

First, open the PhotosRepository and modify it as follows:

class PhotosRepository : Repository, CoroutineScope {
  private val TAG = PhotosRepository::class.java.simpleName
  private val job: Job = Job()
  override val coroutineContext: CoroutineContext
    get() = Dispatchers.Main + job

  //...omitted code...

}

You’ve implemented CoroutineScope, and defined a Job and CoroutineContext.

The Job will determine if the coroutine is active and you will then use to cancel it. Per the main-safe design pattern, the Dispatchers.Main defines the CoroutineScope.

Hooking Into the Life-cycle

Add the following code to the PhotosRepository.

override fun onStop(owner: LifecycleOwner) {
  super.onStop(owner)
  cancelJob()
}

private fun cancelJob() {
  Log.d(TAG, "cancelJob()")
  if (job.isActive) {
    Log.d(TAG, "Job active, canceling")
    job.cancel()
  }
}

Because you previously registered this object as an observer to the Fragment’s life-cycle, Android will call the onStop() method when the Lifecycle.Event.ON_STOP event occurred in the PhotosFragment. Here you’ve canceled the job.

A typical implementation is to include a Job instance plus a Dispatcher as context for the scope. Implementing the interface will let you call launch() at any place and handle cancellation with the Job you provided. The suspend functions can then call withContext(Dispatchers.IO) or withContext(Dispatchers.Default) to delegate work to background threads if necessary, keeping the initial threading tied to the main thread.

Introducing Coroutines

Currently, both the fetchBanner() and fetchPhotos() use a Runnable and execute with a new Thread. Firstly, you have to change the method implementation to use Kotlin coroutines. Then, you’ll run the project, to see if everything works as before.

The banner and images will download in the background. They’ll display like before with the separate background thread implementation.

Modify fetchBanner() and fetchPhotos() methods as follows:

// Dispatchers.Main
private suspend fun fetchBanner() {
  val banner = withContext(Dispatchers.IO) {
    // Dispatchers.IO
    val photosString = PhotosUtils.photoJsonString()
    // Dispatchers.IO
    PhotosUtils.bannerFromJsonString(photosString)
  }
  // Dispatchers.Main
  bannerLiveData.value = banner
}

// Dispatchers.Main
private suspend fun fetchPhotos() {
  val photos = withContext(Dispatchers.IO) {
    // Dispatchers.IO
    val photosString = PhotosUtils.photoJsonString()
    // Dispatchers.IO
    PhotosUtils.photoUrlsFromJsonString(photosString)
  }
  // Dispatchers.Main
  photosLiveData.value = photos
}

The functions above are annotated with comments. They show what thread or thread pool executes each line of code.

In this case, the thread is Dispatchers.Main and the thread pool is Dispatchers.IO. This helps visualize the main-safety design.

Notice that you don’t need to use liveData.postValue() anymore because now you’re setting the Livedata values in the main thread.

Because the modified methods are now marked with suspend, you have to change these function declarations, to avoid compiler errors:

override fun getPhotos(): LiveData<List<String>?> {
  launch { fetchPhotos() }
  return photosLiveData
}

override fun getBanner(): LiveData<String?> {
  launch { fetchBanner() }
  return bannerLiveData
}

Now, build and run the app. Open Logcat, filter with PhotosRepository and then background the app. You should see the following:


PhotosRepository logs

PhotosRepository received an Lifecycle.Event.ON_STOP triggering an active coroutine Job to cancel.

Congratulations! You’ve successfully converted asynchronous code to Kotlin coroutines. And everything still works but looks nicer! :]

Where to Go From Here?

You can download the completed project by clicking on the Download Materials button at the top or bottom of the tutorial.

To continue building your understanding of Kotlin Coroutines and how you can use them in Android app development check resources such as: Kotlin Coroutines by Tutorials book, kotlinx.coroutines and Coroutines Guide.

Please join the forum discussion below if you have any questions or comments.