Kotlin Coroutines in Android — Part 2

The basics

Andrea Bresolin
Kin + Carta Created
7 min readApr 5, 2019

--

Structured concurrency and CoroutineScope

The stable version of Kotlin coroutines introduces the idea of structured concurrency.

In classic asynchronous programming, we have multiple independent blocks of asynchronous code that don’t have a parent-child relationship. They are synchronised through shared objects and they’re cancelled individually. With coroutines, we can still have independent blocks of asynchronous code, but the most convenient, safe and the suggested way of organising them is by having a parent-child relationship. This is achieved through scopes and specifically through an interface named CoroutineScope.

CoroutineScope is usually implemented by an object having a lifecycle that we want to follow. In MVP and MVVM architectures, one obvious lifecycle that we want to follow is the one of the Presenter or the ViewModel. These are the main classes that will implement CoroutineScope.

Let’s take a look at an example implementation of CoroutineScope inside a ViewModel (a Presenter would be similar).

class MyViewModel : ViewModel(), CoroutineScope {

private val job = Job()

override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
}

The concrete implementation of CoroutineScope needs to provide the CoroutineContext. This is the context used to run all the coroutines started within this scope.

The context in this example is made of two components: the dispatcher and a Job.

Dispatcher

The dispatcher decides on which thread our coroutine is going to run. There are four dispatchers provided out of the box by the coroutines library:

  • Main: in Android, runs the coroutine on the main UI thread.
  • Default: runs the coroutine on a background thread part of a thread pool. By default, the number of threads in the pool is at most the number of cores of the CPU and at least 2.
  • IO: runs the coroutine on a background thread part of a thread pool. By default, the number of threads in the pool is at least 64 or the number CPU cores in case this is bigger than 64.
  • Unconfined: this basically runs the coroutine on the current thread without restricting it to any thread pool. It is mainly useful for testing, where we typically want to run the tests on the same thread that started their execution and we want the code to run immediately without offloading to separate threads.

Job

The Job is an interface that identifies a unit of work handled by a coroutine. It is created by a coroutine builder and can be used to keep track of the state of a coroutine or cancel it. A Job can also be created manually as we’ve done inside CoroutineScope to serve as a parent for all the children coroutines started within the same CoroutineContext. This is particularly useful when we want to cancel the whole coroutines hierarchy within a context as we’ll see later in this series.

Coroutine builders

A coroutine builder is a method that creates a new coroutine. There are two main coroutine builders to keep in mind for our needs: launch() and async(). Both these coroutine builders are defined as extensions of CoroutineScope.

launch

Creates a new coroutine and returns a Job to keep track of it. Here is an example:

val job: Job = launch(coroutineContext) {
... coroutine code ...
}

launch() can take an optional CoroutineContext as argument. By default it inherits the context of the CoroutineScope available where it’s called, but by specifying another context explicitly, we can override specific parts of the parent one for example because we want to use a different dispatcher for the block of code inside launch().

launch() doesn’t wait for its coroutine to complete before returning. Let’s take a look at the following code:

val job: Job = launch(coroutineContext) {
(1) ... coroutine code ...
}
(2) ... other code ...

When launch() is called, it returns immediately and the code in (2) is executed. The coroutine code (1) is executed as well in a different coroutine context, so it can be executed on a different thread for example and both (1) and (2) can run at the same time in parallel.

The only case when launch() actually executes (1) completely before returning, is when both the contexts of (1) and (2) use the same thread (for example if they both use the Main dispatcher).

async

Creates a new coroutine and returns a Deferred to keep track of it. Deferred is an interface that extends Job and allows to wait for the result of the coroutine. The main difference between launch() and async() is that the first one doesn’t allow to obtain a value returned by its coroutine block, while the second one allows the block to return a value that can be used outside the coroutine. An example will clarify what we’re talking about:

val deferred: Deferred<T> = async(coroutineContext) {
(1) ... code that returns a result of type T...
(2) return@async result
}
(3) ... other code ...(4) val resultValue: T = deferred.await()(5) ... use resultValue ...

Here’s what happens:

  1. async() starts a new coroutine in the specified CoroutineContext and returns immediately a Deferred object without waiting for its coroutine to complete.
  2. The code in (3) is executed right after async() returns, in parallel with (1) and (2).
  3. In (4), we call the await() method. This is part of the Deferred interface and it basically says “wait for the coroutine started by async() to complete and return its result”. When (1) and (2) complete, the result returned in (2) will be assigned to resultValue in (4).
  4. (5) won’t be executed until the result in (4) is available. We want to wait for the result, so anything after await() won’t be executed until the result is available.

The behaviour of async() is the same that we’ve seen for launch() in case it runs on the same thread of its parent coroutine. If, for example, both async() and its parent coroutine use the Main dispatcher, the code in (1) and (2) would be completely executed before moving to the code in (3).

await() is our first suspend function. A suspend function suspends the coroutine in which it’s called until the result of the function itself is available. Note that suspending a coroutine doesn’t block the thread where the coroutine runs. It simply makes the thread available for other coroutines until it resumes with the result. Remember also that a suspend function can only be called within a coroutine or another suspend function.

Other useful coroutines methods

There are many other useful methods in the coroutines library of course. Among them, one of the most useful in many occasions is withContext(), so let’s take a look at what it does.

withContext

It’s a suspend function that runs a block of code in the specified coroutine context, suspends the current coroutine until the execution has completed and returns the result. Here’s an example:

val resultValue: T = withContext(coroutineContext) {
(1) ... code that returns a result of type T...
(2) return@withContext result
}
(3) ... other code that uses resultValue ...

Given that withContext() is a suspend function, when it’s called, both (1) and (2) execute completely before moving on to (3). Also in this case, as we’ve seen with the launch() and async() coroutine builders, we can specify a coroutine context. This means, for example, that we can run the block of code inside withContext() on a separate thread and resume on the thread of our parent coroutine with the result.

Assume, for example, that our parent coroutine is running on the main Android UI thread. We want to run a block of code on a background thread to avoid blocking the UI. We can use withContext() by passing Dispatchers.Default as its coroutineContext argument so its code will run on a background thread. We wait for it to complete and when it’s done, we can use its result on the main UI thread.

Writing our own suspend functions

Can we write our own custom suspend functions? Of course we can. Here is an example:

suspend fun execute(...): MyResultType {
...
return
MyResultType(...)
}

There’s nothing really special about this function. It’s a standard function that can have some arguments and can return a result. The only difference is the suspend modifier. This is what makes it a suspend function and tells the compiler that this function can suspend a coroutine. The suspension happens when we call another suspend function within the body (for example when we call await() on a Deferred returned by async()).

What’s next

Are you a bit confused at this point? Don’t worry. In the following parts of this series everything will become clearer as we’ll start taking a look at some concrete examples. What we’ve discussed here while talking about coroutine scopes and contexts is a simplified version of the topic. If you want some more information, take a look at a specific post about it.

We’ll always try to avoid providing too many details about all the possible options available in every case and just stick to what’s relevant to avoid further confusion. We didn’t mention the other parameters of launch() and async() for example, but this is to avoid confusion. You can explore all the other coroutines features later when you’ll be familiar with the basics.

One thing that is worth mentioning though, is the viewModelScope extension recently added to ViewModel in version 2.1.0-alpha01 of the Lifecycle package part of Android Jetpack. This extension makes it possible to avoid explicitly implementing the CoroutineScope interface in a ViewModel. That’s handled automatically by the extension, but as a downside, you need to reference viewModelScope every time you want to call the coroutine builders for the top level coroutines (so you would end up writing viewModelScope.launch() or viewModelScope.async() multiple times instead of simply launch() and async()). Given that you’ll probably end up implementing CoroutineScope in a base ViewModel (so just once) and its implementation is very simple, I don’t really see a reason for using the extension. Anyway, the choice is yours.

In the next part, we’ll talk about what working with coroutines looks like specifically in Android Studio.

Get the source code

The source code for this series can be found on GitHub. It’s an example Android project that covers multiple cases. Download it and play with it.

Missed the other parts of this series?

If you’ve missed the other parts of the Kotlin Coroutines in Android series, take a look at the introduction and check the full list of topics.

--

--