Coroutines on Android Made Simple

A clear and straightforward guide to get on board with the latest concurrency design pattern.

Federico Puy
ProAndroidDev

--

Coroutines were a groundbreaking feature for Android Developers since they greatly simplified one of our biggest pain points, asynchronous programming.

However, there are certain concepts and definitions that are quite difficult to grasp, specially if you do not come from an RxJava background, so hopefully this guide will provide a simple and easy way to get you started.

Why you should use Coroutines in the first place?

  • They provide a way to write asynchronous code in a sequential manner, making our code much easier to read.
  • In some way, they are similar to threads, but they are much more efficient, as multiple coroutines can run on a single thread.
  • While the amount of threads you can use in your app is quite limited, you can have as many coroutines as you want.

They provide a way to write asynchronous code, but in a sequential manner.

Suspending functions

As their name suggests, they are functions that suspend the execution of a coroutine at some point, and then resume the execution of the function again when the result is ready.

For example, the implementation of a class that makes a simple request to an API using callbacks:

This implementation, using suspend functions could be written as:

Both have the same functionality, but the second one is much simpler and easier to read, since it seems to be running on a sequential manner, even though it is running an asynchronous operation.

A couple of things to take into account:

  • Suspend functions can only be executed inside another coroutine, or inside another suspending function.
  • Inside a suspend function, you can run asynchronous code, without blocking the main thread.
  • Important -> the keyword suspend does not specify on which thread the code is going to run. Suspend functions can run either on background or in a main thread. So, how do we specify where to run the code inside a coroutine? Using Coroutine Context.

Coroutine Context

It defines a series of rules and configurations that define how and where a coroutine is going to be executed.

Where -> Dispatchers

If you have used Rx before, they could be compared to Schedulers. They define the thread on which a coroutine is going to be executed. Some examples are:

  • Main -> uses the UI Thread. In Rx is similar to AndroidSchedulers.MainThread()
  • Default -> used for CPU intensive tasks. It is going to use as many threads as CPU cores are available. Since they are CPU intensive tasks, there is no point in having many threads running, as the CPU is going to be busy. Similar to Schedulers.computation() in Rx.
  • IO -> as the name suggests, used for Input/Output. Making a request to an API, access to a DB, etc. Similar to Schedulers.io() in Rx.

How -> Coroutine Builders

We can specify different builders depending on what we need to do, or even create one of our own. The ones that already exist are:

  • runBlocking: mostly used for testing, as it executes background tasks in a synchronic way, avoiding the test to finish before the background task ends. Normally, you will not use it in production code. More on this later.
  • launch: the main and most used one. Unlike runBlocking, it won’t block the current thread. Please note that we must specify the Dispatcher that we are going to be using to run it.
  • Also, please note that launch returns a Job object.

Job

It’s essentially a task that runs in background, and it can be interpreted as an action, with a lifecycle that gets destroyed once finished.

You can even establish a hierarchy, with parents and child jobs, so that when you cancel the father, the children also get cancelled.

This allows us to control the lifecycle of a coroutine from one unique place.

We can run different operations using jobs:

Job.join

Blocks the coroutine associated to that job until all the children jobs finish.

Job.cancel

Cancels all the children jobs.

async

Used to start a coroutine that must obtain a result (ex. Api call). This result is represented by an instance of type Deferred (a promise that it is going to return a result in the future), and we must use await to obtain it.

Scopes

All coroutines must run inside a CoroutineScope. This scope defines the coroutines lifecycle through their Job.

When cancelling the job of a scope, all of the coroutines inside that scope get cancelled as well.

Scopes also allow us to define the Dispatcher on which a coroutine is going to run. For example:

  • Global scope -> used for coroutines that should continue executing even after the activity/fragment on which they are running is destroyed. In other words, they will continue running while the app is still doing so.
  • CoroutineScope -> it’s an interface, and we need to set two attributes:
  1. Dispatcher
  2. Job, to cancel the coroutines at the right time.

When we concatenate Dispatchers.Main + job, we are combining their contexts.

After that, we can use launch{} directly anywhere in our Activity/ViewModel to run the coroutine.

IMPORTANT -> Do not forget to cancel the job on onDestroy(), onCleared(), or wherever necessary, in order to avoid memory leaks.

  • viewModelScope & lifecycleScope-> these two were included in AndroidX lifecycle v2.1.0, and as their name suggests, you could use them in a viewModel or LifecycleOwner (Activity or Fragment). They greatly simplify code and reduce boilerplate since they do not require to be canceled/destroyed explicitly. Implementation is pretty straightforward as well:

withContext

Sometimes, we may need to change the context in which a coroutine is running temporarily, so that we can execute a specific action.

For example, for this specific case we have set Dispatchers.Main to be our default dispatcher. However, we need to perform a CPU intensive task, for which Dispatchers.Default is more appropriate.

By using withContext, we can temporarily change the context of a coroutine.

Testing

Let’s assume we want to test the following function:

Since refreshTitle is a suspend function, Kotlin is not going to know how to execute it inside a test, unless we do so from another coroutine or suspend function, and we will receive the following error:

“Suspend function refreshTitle should be called only from a coroutine or another suspend function.”

The test runner doesn’t have a clue on how to run a coroutine, so we can’t make the test to be a suspend function either.

Another alternative would be to use launch(), and use a CoroutineScope, just as we do with a regular coroutine. The problem with this approach is that we need the async call to finish before the test finishes its execution, otherwise the test will always fail.

So, how do tell the test to wait for the coroutine to finish its execution before concluding its result?

Using runBlockingTest(), a function that blocks the execution of the thread when a call to a suspend function is made. This is how we make sure that the result of the asynchronous call finishes before the test does.

Hope the article was useful! I would really appreciate if you could let me know of any suggestions/feedback in the comments. Cheers!

--

--