ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Async code using Kotlin Coroutines

Fabio Collini
ProAndroidDev
Published in
8 min readMay 23, 2018

Kotlin coroutines allow to write asynchronous code in a familiar way, the structure of the code doesn’t change if you compare it with something synchronous. I started to use them some months ago and, after some attempts, I was quite happy with the final result. For example this is the code of a ViewModel that checks the existence of a token in the shared preferences and executes two Retrofit calls (EDIT: updated to Coroutines 0.26.0):

class MyViewModel(
private val tokenHolder: TokenHolder,
private val api: StackOverflowService
) : ViewModel(), CoroutineScope {

private val job = Job()
override val coroutineContext = job + Main fun load() {
launch {
try {
var token = async(IO) { tokenHolder.loadToken() }.await()
if (token.isEmpty()) {
updateUi("Logging in")
token = api.login().await().token
async(IO) { tokenHolder.saveToken(token) }.await()
}

updateUi("Loading data")

val data = api.loadData(token).await()

updateUi(data)
} catch (e: Exception) {
updateUi(e.toString())
}
}
}

private fun updateUi(s: Any) {
//...
}

override fun onCleared() {
job.cancel()
}
}

The load method checks the shared preferences and if a token is not available updates the UI with a loading message and execute a server call to retrieve it. Then it updates again a loading message and executes another server call, the result is shown in the UI.

Thanks to the coroutines every single task is executed in the right thread:

  • the shared preferences are read and written in a background task thanks to the async(IO) invocation;
  • the server calls are executed using Retrofit on a background thread, under the hood the Retrofit coroutines adapter transforms a standard Call object into a Deferred;
  • the UI is updated in the main thread because all the code is wrapped in a launch call using that uses the coroutine context based on the Main dispatcher defined in the class.

Errors are managed using a classical try/catch and, thanks to the job field, the whole task is cancelled when the ViewModel is destroyed. The job + Main syntax is quite strange at the beginning, it’s just an operator overloaded and it means that the scheduler connected to the Main context is used and that the new job is created as a child of the job field.

Everything seemed good, an asynchronous task written in the same way of a synchronous one! But there was something strange: a background task launched using the Main coroutine context.

A lot of material about the coroutines is available, the official guide is a must read but there are many interesting posts on the web. For example, reading these two I learned something new about the coroutines (and some extra doubts about my code arose):

EDIT: I updated all the examples to use the new features available in Coroutines 0.26.0, more info about this update is available in this post.

Coroutines cancellation

EDIT: since version 0.26.0 nested async/launch are connected to the Job used to create the coroutineContext (in the previous example the Job is connected to the ViewModel lifecycle).

EDIT: valid until version 0.25

It seems strange at a first glance but using nested async/launch the internal coroutines are independent: they are not cancelled when the cancel method is invoked on the Job returned by the external one. The official guide contains an example that explains this feature (so it’s totally my fault, I didn’t notice it!).

This problem can be solved in two ways:

passing coroutineContext as first parameter of async/launch the internal coroutines are created as a child of the external one. The cancellation of the external coroutine will be propagated to all the children;

using withContext instead of async/launch a new coroutine is not created, this method allows to change the execution thread of the initial coroutine.

In our example this is not a big issue because we are using nested coroutines just to manage shared preferences. It’s a fast operation and it wouldn’t be cancelled anyway because the cancellation of a coroutine works only if the code is cooperative (obviously the Android code that manages the shared preferences is not aware of the coroutine context). Using Retrofit this problem doesn’t exist because the coroutine Retrofit adapter internally doesn’t use an async/launch.

In the previous example we are using the async method to execute something in a background thread:

var token = async(IO) { tokenHolder.loadToken() }.await()

This syntax is valid but we are creating a new coroutines using async and immediately waiting for the result using await. There is a simpler and more efficient way to achieve the same result: the withContext method.

Let’s change our example, we can easily use withContext to execute the shared preferences code in a background thread:

var token = withContext(IO) { tokenHolder.loadToken() }
if (token.isEmpty()) {
updateUi("Logging in")
token = api.login().await().token
withContext(IO) { tokenHolder.saveToken(token) }
}

updateUi("Loading data")

val data = api.loadData(token).await()

updateUi(data)

We can refactor the code moving the withContext invocation to TokenHolder class (this class uses a delegate to manage the shared preferences, you can find the details in my previous post):

class TokenHolder(prefs: SharedPreferences) {

private var token by prefs.string()

suspend fun loadToken(): String {
return withContext(IO) { token }
}

suspend fun saveToken(token: String) {
withContext(IO) {
this.token = token
}
}
}

The loadToken and saveToken methods must be defined as suspend, in this way we are forcing the execution of these methods inside a coroutine context. Thanks to this keyword and to the IO dispatcher used inside the method we are sure that the shared preferences will never be accessed in the Android main thread. The reason is simple: if we try to invoke these methods directly on the main thread we’ll get a compilation error, if we invoke them inside a coroutine context the body will be executed using the scheduler defined as parameter of withContext.

After this refactoring we can simplify the code of our example:

var token = tokenHolder.loadToken()
if (token.isEmpty()) {
updateUi("Logging in")
token = api.login().await().token
tokenHolder.saveToken(token)
}

updateUi("Loading data")

val data = api.loadData(token).await()

updateUi(data)

Threading

Let’s see a simplified version of the previous example with some extra logs to check the thread where the code is executed:

launch {
try {
log("start")
val token = api.login().await().token
log("where am I?")
val data = api.loadData(token).await()
updateUi(data)
} catch (e: Exception) {
updateUi(e.toString())
}
}

We have already seen that the api calls are executed in a background thread and that the UI is updated on the main thread. The first log is executed at the beginning of a launch block so it will be executed on the main thread. What about the “where am I?” log?

Executing the code we can see in the log something similar to this snippet:

[main] start
--> GET
<-- 200 OK
[main] where am I?
--> GET
<-- 200 OK
[main] updateUi

So the computation goes back and forth between the main thread and some background threads, it:

  • starts in the main thread
  • executes an api call on a background thread
  • saves the token on a local variable and logs a message on the main thread
  • executes another api call on a background thread
  • updates the UI on the main thread

Ok, coroutines are lightweight but the less we use the main thread the better!

So, let’s change the example doing the opposite strategy, we do everything on a background thread and we go on the main thread only to update the UI:

override val coroutineContext = job + Dispatchers.IO//...
fun load() {
launch {
try {
log("start")
val token = api.login().await().token
log("where am I?")
val data = api.loadData(token).await()
withContext(Main) { updateUi(data) }
} catch (e: Exception) {
withContext(Main) { updateUi(e.toString()) }
}
}
}

Looking at the logs again we can notice that the “start” and “where am I?” messages are logged in a background thread:

[ForkJoinPool.commonPool-worker-1] start
--> GET
<-- 200 OK
[ForkJoinPool.commonPool-worker-1] where am I?
--> GET
<-- 200 OK
[main] updateUi

In this second example the main thread is used only when necessary and there are fewer switches between a background thread and the main thread.

We can simplify the code moving the withContext invocation to the updateUi method:

launch {
try {
log("start")
val token = api.login().await().token
log("where am I?")
val data = api.loadData(token).await()
updateUi(data)
} catch (e: Exception) {
updateUi(e.toString())
}
}

After this refactoring we are sure that the UI will be always updated in the main thread. Do you remember the error message Only the original thread that created a view hierarchy can touch its views?

EDIT viewModelScope

The release 2.1.0-alpha01 of the architecture components (in the lifecycle-viewmodel-ktx library) contains a new extension property to use coroutines in a ViewModel. Looking a the source code we can see that this scope uses a Job connected to the ViewModel lifecycle, similar to the previous example of this post, but it uses the Main dispatcher instead of IO. So is this post still useful?

All the theory explained in this post is still valid butviewModelScope is definitely the way to go to use coroutines in a ViewModel. So can we rewrite the previous example using this new scope and avoiding to execute too much code on the main thread? The solution is easy, an extra withContext(IO) block can be used to execute multiple suspending methods without going back and forth from a background thread to the main thread:

var token = tokenHolder.loadToken()
if (token.isEmpty()) {
updateUi("Logging in")
withContext(IO) {
token = api.login().await().token
tokenHolder.saveToken(token)
}
}

Wrapping up: async/launch or withContext?

In this post we have used a lot three coroutines methods: async, launch and withContext. Here there is a recap of how to use them:

  • EDIT: the external call is often an invocation of launch, this method allows to invoke other suspending methods. Exceptions thrown inside a launch invocation must be managed using a try/catch to avoid an app crash;
  • in a test or maybe on some particular use cases runBlocking can be used;
  • withContext is useful to change the execution thread of a coroutine, for example to update the UI in the main thread or to manage the shared preferences in a background thread;
  • a suspending method that uses withContext will be always executed on a certain thread;
  • nested async calls are useful to execute some tasks in parallel.

The final example

This is the final example (it’s available also in this GitHub repository), it’s similar to the initial one but it manages the thread in a better way:

viewModelScope.launch {
try {
var token = tokenHolder.loadToken()
if (token.isEmpty()) {
updateUi("Logging in")
withContext(Dispatchers.IO) {
token = api.login().await().token
tokenHolder.saveToken(token)
}
}

updateUi("Loading data")

val data = api.loadData(token).await()

updateUi(data)
} catch (e: Exception) {
updateUi(e.toString())
}
}

I understood many of the concepts that I have tried to explain in this post during a chat with Roman Elizarov at the Kotlin office hours during Google I/O, thanks a lot Roman!

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Fabio Collini

Android GDE || Android engineer @nytimes || author @androidavanzato || blogger @codingjam

Responses (6)

Write a response

Hi,
I want to sleep for sometime. I tried using "await sleep(1000)"
jsExecute=var getRhinoHello=function(){await sleep(1000) return "hello rhino"}

--

I could not use any external library! Could you please provide more information?
For example how to use the JsonLogic apply function?

--

Hi. I don't understand where you put the Jsonlogic library in your project, and how/where you call it. May you put the full project on Github ? Thanks.

--