Kotlin Coroutines patterns & anti-patterns

Dmytro Danylyk
ProAndroidDev
Published in
3 min readDec 3, 2018

--

Table of Contents

Intro

Decided to write about several things which in my opinion you should and shouldn’t do (or at least try to avoid) when using Kotlin coroutines.

Wrap async calls with coroutineScope or use SupervisorJob to handle exceptions

❌ If async block may throw exception don’t rely on wrapping it with try/catch block.

In the example above doWork function launches new coroutine (1) which may throw an unhandled exception. If you try to wrap doWork with try/catch block (2) it will still crash.

This happens because the failure of any of the job’s children leads to an immediate failure of its parent.

✅ One way how you can avoid the crash is by using SupervisorJob (1).

A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children.

Note: this will work only if you explicitly run your async on coroutine scope with SupervisorJob. So the code below will still crash your application because async is launched in the scope of parent coroutine (1).

✅ The other way how you can avoid the crash, which is preferable, is by wrapping async with coroutineScope (1). Now when the exception occurs inside async it will cancel all other coroutines created in this scope, without touching outer scope. (2)

Alternatively, you can handle exceptions inside the async block.

Prefer the Main dispatcher for root coroutine

❌ If you need to do a background work and update UI inside your root coroutine, don’t launch it with non-Main dispatcher.

In the example above we launch root coroutine using a scope with Default dispatcher (1). With this approach, every time when we need to touch user interface we have to switch context (2).

✅ In most cases, it’s preferable to create your scope with the Main dispatcher which results in simpler code and less explicit context switching.

Avoid usage of unnecessary async/await

❌ If you are using async function followed by immediate await you should stop doing this.

✅ If you want to switch coroutine context and immediately suspend parent coroutine withContext is a preferable way to do that.

Performance wise it’s not a big concern (even thought async creates new coroutine to do the work) but semantically async implies that you want to start several coroutines in the background and only then await on them.

Avoid cancelling scope job

❌ If you need to cancel coroutine, don’t cancel scope job in the first place.

The issue with the above code is that when we cancel job we put it into the completed state. Coroutines launched in a scope of the completed job will not be executed (1).

✅ When you want to cancel all coroutines of a specific scope, you can use cancelChildren function. Also, it’s a good practice to provide the possibility to cancel individual jobs (2).

Avoid writing suspend function with an implicit dispatcher

❌ Don’t write suspend function which relies on execution from specific coroutine dispatcher.

In the example above login function is a suspend function which will crash if you execute it from coroutine which uses non-Main dispatcher.

CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

✅ Design your suspend function in a way that it can be executed from any coroutine dispatcher.

Now we can call our login function from any dispatcher.

Avoid usage of global scope

❌ If you are using GlobalScope everywhere in your Android application you should stop doing this.

Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely.

Application code usually should use application-defined CoroutineScope, using async or launch on the instance of GlobalScope is highly discouraged.

✅ In Android coroutine can be easily scoped to Activity, Fragment, View or ViewModel lifecycle.

Special thanks: Andrey Mischenko, Louis CAD, Bradyn Poulsen, Tolriq, Dave A.

--

--