Kotlin Coroutines: Let it async in

What are Coroutines?

Think of Coroutines as lightweight threads that are used to perform tasks asynchronously, a.k.a. non-blocking programming.

They are natively supported by many programming languages, such as Go, Python, Perl, Ruby, Kotlin, etc.

A Kotlin Coroutine is a feature in Kotlin that lets you write non-blocking, asynchronous code that doesn’t require context-switching.

Who this tutorial is for?

Although Coroutines are used in general-purpose programming quite often, this article will primarily focus on Coroutines in an Android context.

This article will be a guide on how to implement Coroutines and the know-hows on integrating them into your existing Android app. We’ll be taking a look at how to create Coroutines, how they work, and advanced usages to fine-tune it in an Android app.

The prerequisites are as followed:

  • Knowledge of basic Kotlin syntax;
  • Basic knowledge of RxJava2 (Recommended, not mandatory);
  • Basic asynchronous programming experience in Android development;
  • Android Studio 3.2 or above; and,
  • Kotlin 1.3.0 or above.

Your app on the main thread

If you recently got a new phone, chances are it has a refresh rate of at least 60Hz. This means that your app has 16ms to perform tasks on the Android main thread. Keep in mind that there are phones with higher refresh rates, and this time period will vary.

Android phones.png

These tasks are usually performed on the main thread of an Android app:

  • XML parsing
  • View inflation
  • View measurement
  • View positioning

So, as you can see, your app does quite a lot of processing on the main thread, and that’s where the need to perform your tasks on an asynchronous thread arises.

So why Kotlin Coroutines?

Kotlin Coroutines enhance asynchronous programming by being lightweight and essentially faster than a thread as they are stackless. What this means from a multiprocessing perspective, is that Kotlin Coroutines don’t map on the native CPU thread, hence there’s no context-switching on the processor.

Additionally as most phones have at least 4 cores these days, it might be a good idea to put all 4 cores to work!

Adding Kotlin Coroutines to your project

What’s noteworthy when it comes to adding Kotlin Coroutines to your project is that they have been stable since the release of Kotlin 1.3.0. So if you’ve been using any version of Kotlin that’s below 1.3.0, it’s recommended that you upgrade the version in Android Studio IDE. Note: At the time of writing this article, the latest version of Kotlin was 1.3.21.

Here’s how you’d include the latest version of Kotlin in your project-level build.gradle file:

buildscript {
  ext.kotlin_version = '1.3.21'
  dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // Kotlin Gradle Plugin
  }
}

Once you’re done with that, add the following dependencies to your app-level build.gradle file:

dependencies {
  // Core dependency
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
  // Android UI Programming
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
  // RxJava2 & Reactive Programming
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.1.1'
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-reactive:1.1.1'
}

If you’re using ProGuard, you might also want to add the following rules to your proguard-rules.pro file:

-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepclassmembernames class kotlinx.** {
    volatile <fields>;
}

Prelude to launching your first Coroutine

Before we get into launching Coroutines, it’s very important to discuss the two different types of functions in the context of synchronous-asynchronous programming: blocking functions and suspending functions.

Blocking Functions

Simply put, blocking functions are synchronous. If two or more functions execute one after the other, in the order they were invoked, they are said to be blocking functions. As none of them can be “paused” while the others are done executing, there’s less flexibility, in terms of execution, when it comes to blocking functions.

Let’s consider two blocking functions fun A() and fun B(). Here’s how they would be represented:

Kotlin Blocking Functions.png

Suspending Functions

Suspending functions are functions that can be “paused”, or as the name itself indicates, “suspended”.

The above two functions fun A() and fun B(), if made suspending functions, would be represented like this:

Kotlin Suspending Function.png

Here, we've suspend the execution of fun A() until fun B() is done executing, and once it’s done executing, we resume fun A() again.

To sum it up, a suspending function is a function whose execution can be started, paused and resumed again.

In Kotlin, a suspending function can only be invoked from another suspending function. A suspending function can’t be called from a regular (or blocking) function.

To declare a suspending function in Kotlin, just add the suspend modifier to your function. So,

fun A() {
  // Do something here
  // in your normal function.
}

becomes:

suspend fun A() {
  // Do something here
  // in your suspending function.
}

The anatomy of a Kotlin Coroutine

Before we get to using Coroutines in our app, it is very important to understand how Coroutines work under the hood and get a good understanding of the components that are responsible for launching and executing a Coroutine.

It’s important that we take a quick look at four Coroutine concepts here:

  • CoroutineContext
  • CoroutineScope
  • Coroutine Builder
  • CoroutineDispatcher

Kotlin Coroutine Concepts.png

CoroutineContext

CoroutineContext, as the name indicates, defines the context in which your Coroutine runs.

This is similar to the Context of Activity or Fragment, and is used to manage lifecycle-related operations, proper threading, debugging, and handling exceptions.

CoroutineScope

Your Coroutine needs CoroutineContext to run, and this is provided by the interface, CoroutineScope. This way you can restrict the scope of your Coroutine to the lifecycle of Activity or Fragment.

Coroutine Builder

Coroutine Builders are extension functions on your CoroutineScope that let you build your Coroutine and manage its execution.

In Kotlin, we have a bunch of Coroutine Builders. The two most used Coroutine Buillders are launch and async. We will cover this in detail later in this article.

CoroutineDispatcher

CoroutineDispatcher is an abstract class that is responsible for launching your Coroutines. There are 4 Dispatchers that a CoroutineDispatcher can use to launch your Coroutine:

  • Dispatchers.Default: As you can probably tell, this is the default Dispatcher. This launches your Coroutine by using a common pool of threads. Normally, the number of threads used here equal the number of cores present on the device the app is running on.
  • Dispatchers.Main: This is the Dispatcher which confines itself to the Main thread, and hence, only using one thread.
  • Dispatchers.IO: This provides a shared pool of 64 on-demand created threads to your Coroutine. This Dispatcher is used to execute long-running and blocking I/O operations.
  • Dispatchers.Unconfined: This lets your Coroutine run on the current thread, but if it is suspended and resumed, it will run on whichever thread that the suspending function is running on. Please note that an unconfined Dispatcher shouldn’t normally be used in code.

Apart from using these 4 Dispatchers, you can also:

  • convert Executors into Dispatchers by calling the asCoroutineDispatcher() extension function on them; and
  • create private thread pools for your Coroutines with newSingleThreadContext and newFixedThreadPoolContext.

Launching your first Coroutine

The entire list of Coroutine Builders can be found here, but for brevity purposes, we shall only talk about launch and async Coroutine Builders.

Using launch

The launch Coroutine Builder launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job.

Here is the function definition for launch, as described in the official documentation:

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> Unit
): Job (source)

Here, the Job class is used to represent a job of a Coroutine and is also used to manage the execution of said Coroutine. You can cancel a Coroutine’s execution if you cancel a Job.

The diagram below depicts the lifecycle of a Job:

  • NEW: When the Job is first created.
  • ACTIVE: When the Job begins executing.
  • WAIT FOR CHILDREN: When the Job waits for its children Jobs to finish executing.
  • COMPLETED: When the Job and its children Jobs are done executing.
  • CANCELING: If the Job or one of its children fail, or are canceled.
  • CANCELED: When the Job is done being canceled.

Kotlin Coroutines Job.png

Another noteworthy thing here is that the launch Coroutine Builder is actually an extension function on CoroutineScope. We will see how this ties into lifecycle aware components later.

Let’s dive into the three parameters of the launch Coroutine Builder:

  1. context: CoroutineContext: This parameter is used to specify the context in which the Coroutine needs to run. By default, the value passed here is EmptyCoroutineContext, which just provides an empty Coroutine context to your Coroutine.
  2. start: CoroutineScope: This parameter is used to define the way you want to initiate your Coroutine. By default, this is provided an enum value of CoroutineStart.DEFAULT. Here are all the enum values for CoroutineStart:
    • DEFAULT: This value begins to immediately execute the Coroutine.
    • ATOMIC: This is similar to DEFAULT, except a Coroutine started in this mode cannot be cancelled before it begins execution.
    • LAZY: This starts the Coroutine only when it is needed. This is commonly known as lazy initialization.
    • UNDISPATCHED: This immediately starts the Coroutine but the Coroutine will suspend itself when it reaches a point of suspension in the current thread.
  3. block: suspend CoroutineScope.() -> Unit: This is the most important part of your Coroutine Builder. This is where the code that needs to be executed goes. It takes in a suspending block, which is an extension function on CoroutineScope, and returns a Unit.

Let us run an example of launch:

class MyActivity : AppCompatActivity(), CoroutineScope {
  
// Job variable that is tied to the Activity lifecycle
  lateinit var job: Job

  // Overridden from CoroutineScope,
// Main context that is combined with the context of the Job as well
  override val coroutineContext: CoroutineContext
        		get() = Dispatchers.Main + job

  // IO context that is combined with the context of the Job as well
  val ioContext: CoroutineContext
    get() = Dispatchers.IO + job
  
  // Your Coroutine goes here
  fun myCoroutine() = launch {

    // This is a long-running task that runs with the coroutineContext
    longRunningTask()

    val longerJob: Job = launch(context = ioContext) {
      // This is another long-running task that runs with the ioContext
      anotherLongRunningTask()
    }
  }

  // Method to wait for all Coroutines to finish executing
  // Use the join() method to suspend the Coroutine until it’s done executing
  fun waitForAllCoroutinesToFinish() = job.join()

  override fun onCreate(...) {
    super.onCreate(...)
    
    // Create the Job variable in Activity’s onCreate()
    // This job was earlier declared as lateinit and we’re initializing it here
    job = Job()
  }

  override fun onDestroy() {
    super.onDestroy()

    // Clean up your Job variable by canceling it in the Activity’s onDestroy()
    job.cancel()
  }
}

In the above code, we’re running two Coroutines: one with the Dispatchers.Main dispatcher, and one with the Dispatchers.IO dispatcher. Both of these dispatchers tie into the Job that we’re creating when the Activity is first created. When the Activity is destroyed, we are canceling our Job, and hence, all Coroutines and their children Coroutines also be canceled.

Note: Make sure that you’re implementing CoroutineScope in your Activity and overriding coroutineContext. This is needed for your Coroutine to work.

Using async

The async Coroutine Builder is the same as launch, with the exception that it returns a Deferred<T>.

Here’s the method definition for async:

fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> T
): Deferred<T> (source)

This is the key difference between async and launch. Deferred<T> returns a particular value of type T after your Coroutine finishes executing, whereas Job doesn’t.

Think of it like this: launch is more of a fire-and-forget Coroutine Builder, while async actually returns a value after your Coroutine is done executing.

Note: A Deferred<T> extends a Job. So you can call cancel() on it like you normally would to cancel your Coroutine. It doesn’t have the join() method, but has the await() method instead. This is a return type of T, i.e. it waits until your Coroutine is done executing and returns the resultant variable.

Since Deferred<T> extends Job, the lifecycle remains the same.

Let us run an example of async:

class MyActivity : AppCompatActivity(), CoroutineScope {
  
// Job variable that is tied to the Activity lifecycle
  lateinit var job: Job

  // Overridden from CoroutineScope,
// Main context that is combined with the context of the Job as well
  override val coroutineContext: CoroutineContext
        		get() = Dispatchers.Main + job

  // IO context that is combined with the context of the Job as well
  val ioContext: CoroutineContext
    get() = Dispatchers.IO + job

// Your Coroutine goes here
  fun myCoroutine() = launch {

    // This is a long-running task that runs with the coroutineContext
    // This returns a Deferred value of type String
    val deferred: Deferred<String> = async(context = ioContext) {
getResponseFromUrl() 
}

    // Waits for the Coroutine to finish and return the result
    val resultString: String = deferred.await()
  }

  // Method to wait for all Coroutines to finish executing
  // Use the join() method to suspend the Coroutine until it’s done executing
  fun waitForAllCoroutinesToFinish() = job.join()

  override fun onCreate(...) {
    super.onCreate(...)
    
    // Create the Job variable in Activity’s onCreate()
    job = Job()
  }

  override fun onDestroy() {
    super.onDestroy()

    // Clean up your Job variable by canceling it in the Activity’s onDestroy()
    job.cancel()
  }
}

In the code above, we’ll get a String from a long-running network call via our async Coroutine Builder. We’re using the ioContext to do so and storing the Deferred<String> value in deferred. This way we can get the actual value once the Coroutine is done executing.

We'll call the deferred.await() method immediate after. Since the await() method here returns a String, we’ll store it in our variable resultString of type String.

Where do I go from here?

We have explored Coroutines in detail and learned how to build them with the help of async and launch.

While these are basic usages of Kotlin Coroutines, we encourage you to explore this concept in depth with the following set of resources:

Additionally, here is a helpful cheatsheet that you might want to keep on your desk while writing Coroutines.

Conclusion

As we’ve seen, Kotlin Coroutines are an easy way to write asynchronous, non-blocking code. If used properly, we can reduce the boilerplate code that comes along with writing asynchronous code in Android.

Kotlin Coroutines are highly fine-tunable and a great solution to problems that commonly trouble Android developers when writing asynchronous code.

So go ahead, explore Coroutines in depth today and let them do all the heavy-lifting for your Android app!

Last updated on Apr 16, 2019