Coroutines: simplify threading on android

Android runs on Java and by default Java threads execute once, and once only. On a platform like Android where the UI is constantly updating, creating new threads for every action quickly becomes expensive. To get around this Android’s main thread uses the HandlerThread which is a subclass of the Java Thread. Android keeps this main thread alive by using a Looper which schedules Messages (Runnables) to execute.

These messages are run sequentially, which is why performing long running messages on the main thread will block subsequent messages waiting to be executed. This is why network calls, database calls, and long running operations, must all be run on an alternative thread — to avoid blocking the main thread (the UI).

Threading is complicated

As can be seen by the adoption, and abandonment of a variety of popular threading methods: AsyncTask, ThreadExecuters, EventBus, RxJava (to be abandoned?). So are coroutines any different? And can coroutines simplify threading architecture on Android?


Coroutines

First add these lines to your build.gradle file.

// Ensure you are using Kotlin 1.3
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'

And you are ready to go.

fun simpleThreadingExample() {
GlobalScope.launch {
myApi.makeNetworkCall()
}
}

This is the most basic form asynchronous calls can take with coroutines. The GlobalScope defines a common pool of shared threads which all scoped coroutines will run on unless defined otherwise. By invoking the launch coroutine from GlobalScope we scope the coroutine and run it on this common pool of shared threads. If you don’t care about anything else, stop here. Your app is asynchronous.


Localising scope

The GlobalScope stays alive for the entire lifetime of the application so the coroutine library recommends you do not use this scope. In an Android app the majority of blocking calls are scoped to specific Activities, Fragments, or Views. So lets create our own CoroutineScope, and in addition, we will define which thread these scopes will run on.

// This will usually be defined in your BasePresenter
protected val myScope = CoroutineScope(Dispatchers.IO)
fun simpleThreadingExample() {
myScope.launch {
myApi.makeNetworkCall()
}
}

Dispatchers allow us to define the thread a coroutine runs on. Dispatchers.IO defines a shared pool of threads and all coroutines belonging to myScope will run on these IO threads.

By defining our own CoroutineScope we now have better control of the coroutines we launch, which will later allow us to cancel coroutines when certain lifecycle events occur.


Displaying a result

Usually we want to return a result from blocking calls. This can then be displayed to the user. But to display the result, we need access to the view— on the main thread.

// This will usually be defined in your BasePresenter
protected val myScope = CoroutineScope(Dispatchers.Main)
fun updateView() {
myScope.launch {
val user = withContext(Dispatcher.IO) {
myApi.getUser()
}
view?.showUser(user)
}
}

To achieve this we first use the Dispatcher.Main for our CoroutineScope. This ensures all scoped coroutines run on the main thread. However, we still need a way to switching to a background thread within a Coroutine.

This is where withContext comes in. We use the withContext coroutine with the Dispatcher.IO to execute the blocking call off of the main thread. The parent coroutine (launch) suspends while the withContext coroutine is in progress, allowing the main thread to continue unimpeded. When withContext returns a result, the launch coroutine becomes active and the view is updated on the main thread.


Providing dispatchers

While we’re here, lets ensure we only define each Dispatcher once by creating a provider class and passing this class to wherever a coroutine is launched from.

class DispatcherProviderImpl : DispatcherProvider {

override val main: CoroutineDispatcher
get() = Dispatchers.Main

override val background: CoroutineDispatcher
get() = Dispatchers.IO
}
class BasePresenter(
dispatcherProvider: DispatcherProvider
) {
  protected val myScope = CoroutineScope(dispatcherProvider.main)
}
class MainPresenter(
private val view: View,
private val myApi: MyApi,
dispatcherProvider: DispatcherProvider,
) : BasePresenter(dispatcherProvider) {
fun updateView() {
myScope.launch {
val user = withContext(dispatcherProvider.background) {
myApi.getUser()
}
view.showUser(user)
}
}

Now our Dispatchers are defined in only one place. If we want to change the thread all our background calls run on, we simply change the Dispatcher here. The additional benefit comes when we test our code. When we initiate our class under test, instead of passing a mock to the constructor we can pass a TestDispatcherProvider. The TestDispatcherProvider always returns Dispatchers.Unconfined which is similar to the Trampoline scheduler in RxJava.

/**
* Defined in your test package
*/
class TestDispatcherProvider : DispatcherProvider {

override val main: CoroutineDispatcher
get() = Dispatchers.Unconfined

override val background: CoroutineDispatcher
get() = Dispatchers.Unconfined

}

Now all tests will run synchronously on the main thread. Allowing unit tests like this.

class MainViewModelTest {

private val dispatcherProvider = TestDispatcherProvider()
private val view: View = mockk()
private val myApi: MyApi = mockk()
private val presenter = MainPresenter(
view,
myApi,
dispatcherProvider
)

@Test
fun `User is displayed`() {
val user = aUser()
every { myApi.getUser() } returns user
    presenter.performBlockingTaskAndUpdateView()

verify { myApi.doBlockingTask() }
verify { view.showUser(user) }
  }
}

Our individual unit tests aren’t even aware that asynchronous code exists. We’re testing the logic of the unit. Not the thread the logic occurs on.


Error handling

You may notice that if an error were to occur, we don’t handle it. The app would simply crash. So we need a way to catch errors, and take an action on the view to inform the user.

fun handleError() {
myScope.launch {
try {
val user = withContext(dispatcherProvider.background) {
myApi.getUser()
}
view?.showUser(user)
} catch (exception: Exception) {
// Handle custom errors here
view?.showError()
}
}
}

Here, we can wrap our blocking call in a try-catch. If an error occurs while the call is in progress, we catch the error, check the exception, and display an error to the user.


Cancelling coroutines

In Android we also have to worry about asynchronous calls returning after the user backgrounds the app. You don’t want to cause a crash by updating the view while the app is backgrounded. So lets make our coroutines cancelable onPause.

abstract class BasePresenter {

protected val coreroutineSupervisor = SupervisorJob()
protected val myScope = CoroutineScope(
dispatcherProvider.main + coreroutineSupervisor
)
    // Call this from the lifecycle method of your android component
@CallSuper
override fun onPause() {
coreroutineSupervisor.cancel()
}
}

Every CoroutineScope has a Job. If you don’t initialise the scope with a Job, it is created by default. Every created coroutine also returns a Job, which is added as a child of the CoroutineScope Job. This ensures that all coroutines can be cancelled when the scope is ended.

The default behaviour of a Job is that if any child fails, all Jobs end — every child is linked to every other child. This is where the SupervisorJob comes in, the supervisor enables children to fail independent of other children.

And when our Android component reaches a paused state, we simply cancel the supervisor job. Which cancels all children jobs. So a null view is never called.

You may have noticed that there has been no mention of suspend, Deferred, async {} and await(). The reason is that combining launch and withContext ensures all long running actions occur on another thread. We can call a function, a database, or the network, and be safe in the knowledge that all these actions are non-blocking.

Why is this important? Because threading is complicated, threading makes tests more complex, and the way we thread, like a library, can change — think of AsyncTasks, ThreadExecuters, EventBus, and RxJava. By restricting threading to a single layer (the presenter layer), we can write UseCases, Repositories, Networking, Caching, and Databases as simple synchronous code.

There you have it, only one layer in your app knows about threading. The rest of your app is standard synchronous code — easy to test and easy to understand.

As ever architecture is always evolving, and there is no single ‘right’ approach. I’d love to hear any feedback you may have on this approach. Additionally, as coroutines are so new, I’m sure there’s something that has been missed so if you spot any problems or improvements please drop a comment!