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 Luka Kordić.

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.

Creating Your First Coroutine

Open TutorialFragment.kt, and navigate to downloadSingleImage. Replace // TODO: Not implemented with the following code:

lifecycleScope.launch {
 val originalBitmap = getOriginalBitmap(tutorial)
 val snowFilterBitmap = loadSnowFilter(originalBitmap)
 loadImage(snowFilterBitmap)
}

In this method, you combine two concepts from the Kotlin Coroutines API to launch a new coroutine and execute some code inside it. lifecycleScope is a coroutine scope and launch is a coroutine builder. You simply call launch on an instance of CoroutineScope and pass a block of code that you want to execute. This is all it takes to create a new coroutine and execute some code in it. Simple, right? :]

A great thing when using Kotlin Coroutines is that the code you write is sequential and looks pretty much like regular blocking code. Once you call getOriginalBitmap, the coroutine will suspend until the bitmap is ready. Meanwhile, the thread this code runs in is free to do other work. When the bitmap becomes available, the coroutine will resume and will execute loadSnowFilter and loadImage after that.

Before you proceed to the next section, where you’ll learn more about coroutine builders, examine getOriginalBitmap:

//1
private suspend fun getOriginalBitmap(tutorial: Tutorial): Bitmap =
//2
withContext(Dispatchers.IO) {
  //3
  URL(tutorial.imageUrl).openStream().use {
    return@withContext BitmapFactory.decodeStream(it)
  }
}

This method is invoked from inside the coroutine you’ve just created. Here’s a breakdown of what it does:

  1. Notice that the method is marked with the suspend modifier. This means that the method has the ability to suspend the execution of a coroutine it’s currently running in. This is really important in this case because the method is doing a heavy operation that could potentially block the main thread.
  2. withContext(Dispatchers.IO) makes sure that you switch the heavy work to a worker thread. You’ll learn about Dispatchers and withContext in the following sections. For now, just remember that this is used to offload the work to another thread.
  3. You open a connection to the specified URL, which returns an instance of InputStream for reading from that connection. This piece of code downloads the image.

Build and run the app to see what you’ve done so far:

Kotlin tab screenshot

You’ll see a Kotlin image with the snow filter applied in the first tab. If you try to navigate to other tabs, you’ll only see a placeholder image. That’s OK for now — you’ll fix it later. Right now, it’s time to learn a bit more about coroutine builders.

Coroutine Builders

You can create new coroutines in a couple of different ways. The API has several constructs at your disposal, each intended for a different purpose. Since this is an introduction to Kotlin Coroutines, you’ll learn only about the essential ones:

  • launch: The most often used coroutine builder. It creates a new coroutine and returns a handle to the newly created coroutine as a Job object. You’ll learn how to use jobs for canceling a coroutine in a later section. You use this builder when you don’t need to return a value from the coroutine.
  • async: Used when you want to return a value from a coroutine in a postponed way. It launches a new coroutine and returns a Deferred object, which contains the operation’s result. To get a value from the Deferred object, you need to call await on it. You can also use this builder to do things in parallel. You’ll see this later in the tutorial when you download two images.

Notice that you invoke the launch builder on lifecycleScope. Both async and launch are defined as extension functions on CoroutineScope. Check the following section for more details about the coroutine scope.

Coroutine Scope

A coroutine scope determines how long a coroutine lives. It does that by providing a parent context to coroutines created inside it. That’s why every coroutine builder is defined as an extension function on the scope.

In Android apps, you have two predefined scopes ready to be used: lifecycleScope and viewModelScope. In downloadSingleImage, you used lifecycleScope, which is tied to the lifecycle of the current lifecycle owner. This means that all coroutines created in lifecycleScope will be canceled once this fragment is destroyed. This is a great concept because you don’t need to keep track of the coroutines manually. Everything’s done automatically for you. As a general rule, you should use the predefined scopes to create your coroutines.

GlobalScope

Coroutines API also contains GlobalScope. This scope stays active as long as an app is alive. It’s considered a delicate API because it can be easily misused and cause memory leaks. You can check the official docs for more information about it. You won’t use it in this tutorial.

Downloading Images in Parallel With async

For the Kotlin tutorial, you only downloaded one image. Other tutorials in the app have two image URLs. In this section, you’ll use the async coroutine builder to download both images in parallel.

Open TutorialFragment.kt, and navigate to downloadTwoImages. Replace // TODO: Not implemented with this code:

// 1
lifecycleScope.launch {
  // 2
  val deferredOne = lifecycleScope.async {
    getOriginalBitmap(tutorial)
  }
  // 3
  val deferredTwo = lifecycleScope.async {
    val originalBitmap = getOriginalBitmap(tutorial)
    loadSnowFilter(originalBitmap)
  }
  // 4
  loadTwoImages(deferredOne.await(), deferredTwo.await())
}

Here’s what the code does:

  1. Launches a new coroutine in lifecyleScope. This is the same as in the previous example.
  2. Creates a new coroutine in lifecycleScope, returns an implementation of Deferred and stores it in deferredOne‘s value. This coroutine will download and show the original image.
  3. Creates a new coroutine in lifecycleScope, returns an implementation of Deferred and stores it in deferredTwo‘s value. This coroutine will download and show the original image with the snow filter applied.
  4. Calls await on both deferredOne and deferredTwo. This suspends the coroutine until both of the values are fully computed.

When you create a new coroutine by using async, the system starts its execution immediately, but it also returns a future value wrapped in a Deferred object.

To get the value, you need to call await on the deferred instance. If the value isn’t ready yet, the coroutine will suspend. If it’s ready, you’ll get it back immediately.

This is a very powerful concept, and it can significantly speed up your code when you need to perform several long-running operations. But what if you have only one piece of work and you need to return its result? You’ll find that answer in the next section.

Build and run the app and navigate to the Android tab to see the two images:

two images in snowy app