Kotlin Coroutines Tutorial for Android: Getting Started

In this Kotlin Coroutines tutorial, you’ll learn how to write asynchronous code just as naturally as your normal, synchronous code. By Amanjeet Singh.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Coroutine Terminology

Kotlin Coroutines give you an API to write your asynchronous code sequentially. Take this snippet of code for example:

val snowyBitmap = getFilteredBitmap()
showBitmap(bitmap)

Here, showBitmap() uses the snowyBitmap from getFilteredBitmap(), which fetches the bitmap from a given API and applies a snow filter.

But this sequential code can also perform blocking operations, which would be better off in a different thread. You can do this by wrapping it with Kotlin Coroutines. But before that, let’s review some terminology for the Kotlin Coroutines API.

  • Suspending functions: This kind of function can be suspended without blocking the current thread. Instead of returning a simple value, it also knows in which context the caller suspended it. Using this, it can resume appropriately, when ready.
  • CoroutineBuilders: These take a suspending lambda as an argument to create a coroutine. There are a bunch of coroutine builders provided by Kotlin Coroutines, including async(), launch(), runBlocking.
  • CoroutineScope: Helps to define the lifecycle of Kotlin Coroutines. It can be application-wide or bound to a component like the Android Activity. You have to use a scope to start a coroutine.
  • CoroutineDispatcher: Defines thread pools to launch your Kotlin Coroutines in. This could be the background thread pool, main thread or even your custom thread pool. You’ll use this to switch between, and return results from, threads

All of these terms will sink in, once you start working with Kotlin Coroutines in the project.

Adding Kotlin Coroutines support

Now, let’s return to the example. You can solve the problem of blocking, by wrapping the getFilteredBitmap function inside an async block.

val snowyBitmap = async { getFilteredBitmap() }
// do some work
showBitmap(bitmap.await())

That marks showBitmap() as a suspension point for the compiler, because it calls for the result of async(). Now, as soon as the program needs a snowyBitmap, it can await() the result. This will try to fetch the filtered bitmap. async(), runs immediately, wrapping the value in another construct. This construct is called Deferred. Until you actually wait for the value, you don’t suspend or block the caller.

This means that if there’s any other code between the function call and its result, that code can execute freely. Also, by the time you call await() if the value is computed, you can simply resume with the rest of the code. This is a really powerful feature of Kotlin Coroutines.

And it’s only a small piece of what Kotlin Coroutines have to offer, but to fully explore them, let’s switch to Snowy! :]

Setting Up Your Code

Before you can create coroutines, you have to add the dependencies to your Android project. Start by opening the app level build.gradle and adding the following dependencies:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'

These dependencies are important for integrating the different components of coroutines in your project. They and also provide Android support of coroutines for your project, like the main thread dispatcher.

When you start working with Kotlin Coroutines, you’ll be hearing a lot about Jobs. It’s one of the basic constructs in the Kotlin Coroutines API. As such, it’s vital to understand what it does.

Coroutine Jobs

Open TutorialFragment.kt and a property named parentJob of type Job as follows, under the companion object:

private val parentJob = Job()

As the name suggests, a Job represents a piece of work that needs to be done. Additionally, every Job can be cancelled, finishing its execution. Because of that, it has a lifecycle and can also have nested children. Coroutine builders like launch() and async() return jobs as a result.

If you create a child for a Job, by nesting coroutines, it forms a parent-child hierarchy. With the hierarchy, you can control all the children through this single instance of a Job. If you cancel the parent, all of the children get canceled too. If a child fails in execution, the rest of the hierarchy fails, as well.

Describing a parent-child hierarchy helps you to control the lifecycle of coroutines from a single instance when using it inside of an Activity or Fragment.

This is why you’re declaring a parentJob. You can use it to cancel and clear up all coroutines which you launched in TutorialFragment.

Next, it’s important to understand how threading works with coroutines.

Using Dispatchers With Kotlin Coroutines

You can execute a coroutine using different CoroutineDispatchers, as mentioned before. Some of the available CoroutineDispatchers in the API are: Dispatchers.Main, Dispatchers.IO and Dispatchers.Default.

You can use these dispatchers for the following use cases:

  • Dispatchers.Default: CPU-intensive work, such as sorting large lists, doing complex calculations and similar. A shared pool of threads on the JVM backs it.
  • Dispatchers.IO: networking or reading and writing from files. In short – any input and output, as the name states
  • Dispatchers.Main: recommended dispatcher for performing UI-related events. For example, showing lists in a RecyclerView, updating Views and so on.

You’ll use some of these dispatchers to switch between the main and background threads. One last step before you can launch coroutines – defining a CoroutineScope.

Scoping Kotlin Coroutines

Now, to define the scope when the coroutine runs, you’ll use a custom CoroutineScope to handle the lifecycle of the coroutines.

To do it, declare a property, as shown below, and initialize it under the parentJob:

private val coroutineScope = CoroutineScope(Dispatchers.Main + parentJob)

The plus() operator helps you create a Set of CoroutineContext elements, which you associate with the coroutines in a particular scope. The contexts and their elements are a set of rules each Kotlin Coroutine has to adhere to.

This set of elements can have information about:

  • Dispatchers, which dispatch coroutines in a particular thread pool and executor.
  • CoroutineExceptionHandler, which let you handle thrown exceptions.
  • Parent Job, which you can use to cancel all Kotlin Coroutines within the scope.

Both the CoroutineDispatcher and a Job implement CoroutineContext. This allows you to sum them – using plus(), to combine their functionality.

Now it’s finally time to launch some Kotlin Coroutines!

Downloading Images With Kotlin Coroutines

Now, you’ll write a coroutine to download an image from a URL. Add the following snippet at the bottom of TutorialFragment.kt:

// 1
private fun getOriginalBitmapAsync(tutorial: Tutorial): Deferred<Bitmap> =
  // 2
  coroutineScope.async(Dispatchers.IO) {
    // 3
    URL(tutorial.url).openStream().use {
      return@async BitmapFactory.decodeStream(it)
    }
}

Here’s what this code does:

  1. Creates a regular function, getOriginalBitmapAsync(), which returns a Deferred Bitmap value. This emphasizes that the result may not be immediately available.
  2. Use the async() to create a coroutine in an input-output optimized Dispatcher. This will offload work from the main thread, to avoid freezing the UI.
  3. Opens a stream from the image’s URL and uses it to create a Bitmap, finally returning it.

Next, you have to apply the image filters to the result!