Skip to content

Coroutine Basics: Asynchronous Programming with Kotlin

Exactly 21 hours ago from when I’m writing this sentence, I got a comment on my Essential Libraries February 2019 post telling me one of my entries was outdated for a 2019 list. If he said it was Butterknife, sure I’d probably agree (I admit that was an ass-pull).

To my surprise however, he was referring to RxJava! I’ve been advocating RxJava for a little while because well, it just does a lot of things. Asynchronous programming, multithreading, operators, transformations, mapping and all that good stuff.

And the proposed superior to RxJava? It’s Coroutines, embedded into Kotlin. Now this was the first I’ve ever heard about coroutines, I advocate the use of Kotlin, I think it’s the way forward for Android Development, but I can already think of a few reasons for its superiority.

Kotlin only keeps getting better each year and it won’t be long til the vast majority of apps around the world will be written in Kotlin. RxJava does work beautifully with Kotlin but if you’ve got asynchronous tools built into the language itself, it’s probably a bit more optimized both in terms of coding and performance.

So let’s look into it, see what it can and cannot do, it’s pros and cons over RxJava and whether or not it truly is the new definitive approach to asynchronous programming.

If you want to follow along, make sure your Kotlin is updated to version 1.3 or above and have the coroutines dependency installed.

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'

What’s a Coroutine

Banner from kotlinlang.org. This is great but looks so much like an ad. Can I get revenue for this?

We can think of a coroutine as a lightweight thread. Several can run in parallel, wait, and communicate with each other just as a regular thread would. The only difference is that a coroutine costs almost nothing in terms of a performance whereas true threads are pretty expensive.

Coroutines concern themselves with 3 major concepts:

  • Suspend Functions
  • Coroutine Context (Scopes and Jobs)
  • Coroutine Builders (Launch and Async)

Suspend Functions: The meat of Coroutine Programming

@UiThread
suspend fun someSlowMethod(): Int {
    delay(5000)
    return 1
}

If someSlowMethod was any normal method, it would be blocking the main thread but we’ve defined it here as a suspend function. This keyword tells Kotlin to make use of coroutines to run the function. In other words, while the function is still executing and waiting for a result, it unblocks the thread it’s running on so other functions and coroutines can run in the meantime.

You may have also noticed I annotated the function with @UiThread. This is just a quick way to specify the thread where the function runs. Not really a part of coroutines but worth a mention.

Coroutine Context – Scopes and Jobs

val mJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + mJob)

All coroutines run inside a scope. A job can be a single coroutine task, or it could be the parent of a whole hierarchy of them. As such, you can cancel jobs and cancelling a parent job will also cancel any of its children.

The Dispatchers.Main passed in the Scope specifies the scope to be on the main thread. Passing this together with the Job defines the context of the scope and allows the Job to act as a parent job for anything within the scope.

Other dispatchers are as follows:

Dispatchers.Default – Appropriate for CPU intensive tasks. Backed by a shared pool of threads and limited by the number of CPU cores. Analogous to RxJava’s Schedulers.computation.

Dispatchers.IO – Best used for non-CPU intensive background tasks like network and database calls. Also backed by a shared pool of threads and analogous to RxJava’s Schedulers.io.

Dispatchers.Unconfined – Runs coroutines unconfined to any specific thread.  Unconfined coroutines start in the current thread and resumes in any thread switched to in the coroutine function. (Ex. Calling delay will switch to the Default scheduler, thus the coroutine will resume there, even if it was started in the Main thread).

Coroutine Builders – Running Suspend Functions from Regular Functions

Launch

If you try to call a suspend function from say, onCreate, you’ll get an error. Suspend functions can only be run from other suspend functions, or by using the launch keyword.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    uiScope.launch {
        val slowResult = someSlowMethod()
        Log.d(LOG_TAG, "Slow Method: $slowResult")
    }

    val quickResult = quickMethod()
    Log.d(LOG_TAG, "Quick Method: $quickResult")
}

Now we can run suspend functions from pretty much anywhere. In this code, even after quickResult is written after launch, it returns long before slowResult. Everything within launch is asynchronous.

However, everything within launch follows a strict sequential order. Take a look at this code for example

uiScope.launch {
    Log.d(LOG_TAG, "A slow method: ${slowMethod()}")
    Log.d(LOG_TAG, "A slightly faster method: ${slightlyFasterMethod()}")
}
Result Order:
1. Slow Method
2. Slightly Faster Method

Both “slow methods” run asynchronously to code outside of launch, but the slightly faster method still waits for the slow method to complete before it even starts. Now we can perhaps wrap each method in their own instances of launch, and that will work if you want to run the methods independently of each other.

You may want to execute both suspend functions in sequential order, but you want them to run asynchronously together so they execute as soon as the slowest method finishes.

Async

uiScope.launch {
    val slowResult = async { slowMethod() }
    val slightlyFasterResult = async { slightlyFasterMethod() }

    Log.d(LOG_TAG, "Slow Method: ${slowResult.await()}")
    Log.d(LOG_TAG, "Slightly Faster Method: ${slightlyFasterResult.await()}")
}
Result Order:
1. Slow Method
2. Slightly Faster Method

Async does exactly that for us. The result order is still the same, but this time, both methods ran asynchronously together so by the time slow method finishes, the slightly faster method had already finished before then.

The difference between launch and async is that launch uses a ‘fire-and-forget’ kind of execution, while async works more like a promise. Launch returns a Job while Async returns a Deferred (an implementation of job). On the Deferred object, you have to call await to get the result, but doing so allows the kind of asynchronous execution displayed above.

Now I’m no animator but this shows how launch and async work.

Imgflip ftw

Checking Job State

While jobs are being executed, you can call any of its boolean properties to check its status:

isActive – True when the job has started but is not yet complete or cancelled

isComplete – True when a job has either been cancelled or completed with success or failure

isCancelled – True when a job is.. well… cancelled

Cancelling Jobs

override fun onDestroy() {
    super.onDestroy()
    uiScope.coroutineContext.cancel()
}

When the user navigates away, you want to make sure to cancel any jobs associated with the current screen so unneeded coroutines aren’t running, by calling cancel on the coroutine context.

Integrating Coroutines in the ViewModel

Beautiful development is all about structure, and MVVM is one of the ways to go about it. The best way to associate coroutines with a user screen is by placing them in the View Model where intensive operations would normally be placed.

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-alpha04'

It does appear we have an extension from the AndroidX Alpha library for just this.

class MyViewModel: ViewModel() {

    var mJob = Job()
    var viewModelScope = CoroutineScope(Dispatchers.IO + mJob)

    fun getThingimajigs() {
        viewModelScope.launch { fetchThingimajigs() }
    }

    ...
}

Now with this library, any scope within a View Model will automatically cancel any coroutines when it is cleared, meaning we won’t have to keep writing the boilerplate code with onDestroy when we define coroutines in the View Model.

Another thing is that scopes will propagate themselves. That means if a coroutine starts another coroutine, they’ll fall under the same scope which is great if you happen to use libraries that also make use of coroutines.

Anyways

That’s about it. This is the first of a 2-part series, the second of which I’ll be comparing coroutines to RxJava and see which truly is the way to go for asynchronous programming in Android. Happy Coding.