Kotlin Coroutines

Introduction

There are two types of multitasking methods to manage multiple concurrent processes. In one type the OS controls the swich bewteen processes. The other type is called cooperative multitasking, in which processes control their behaviour by themselves.

Coroutines are software components that create subroutines for coporative multitasking. A coroutine can be introduced as a sequence of well managed sub tasks. To some extend, a coroutine can be considered as a light weight thread. You can execute many coroutines in a single thread. A coroutine can also be swiched between threads, and can be suspended from one thread and resumed from another thread.

In kotlin there’s a coroutine library since 1.3, which avoids us the need for RxJava, AsyncTask, Executors, HandlerThreads and IntentServices. The coroutines API allows us to write asynchronous code in a sequential manner.

Importance of coroutines

For a phone with a 90hz screen, our app has only 11ms (1000 ms / 90 refresh per second) to perform tasks in the Android main Thread, in between screen refreshes. By default android main thread has a set of regular responsabilities such as:

  • always parse xml
  • inflate view components
  • draw them again and again for every refresh
  • attend click events

If we add more tasks to the main thread, if its execution time exceeds this super small time gap between two refreshes, the app will show performance errors. The Screen might freeze. As a result of this, we should always implement long running tasks async in a separated thread.

Coroutines vs Threads

Couroutines are NOT the same as Threads. We have Main or UI Thread. We also have on demand back ground worker threads, but threads are not equal to coroutines. Any of those threads can have many coroutines running in it, at the same time.

Basic Implementation

We need to implement both core and Android dependencies.

dependencies {
	implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-RC2"
	implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-RC2"
}

Let’s start with CoroutineScope. This is an interface which provides the scope of the coroutine. We need to run the task in a background thread, so we provide Dispatchers.IO. launch{} is the coroutine builder.

btnDownload.setOnClickListener {
	CoroutineScope(Dispatchers.IO).launch {
		downloadData()
	}
}

The function downloadData() will execute inside our new coroutine.

Coroutines Scope

In an App we can launch hundreds coroutines running at the same time. By default, coroutines don’t help us to keep track of them or any work that’s being done by them. If we don’t manage them carefully, there can be leaks in memory. To solve this, we must start all coroutines within a scope. Using the scope, we can easily keep track of coroutines, cancel them and handle errors or exceptions. We do this through the CoroutineScope interface.

There’s another interface called GlobalScope (very rarely used) which is used to launch top-level coroutines, which are operating on the whole application lifetime.

Coroutines Dispatchers

Both of these scopes also acts as a reference to the coroutine context. (Dispatchers.IO). We can include the name of a Job instance, plus a Dispatcher as the context of the job. This describes the kind of thread where the coroutine should be run. It’s recommended to start coroutines using Main Thread and then switch to Background Threads.

We have:

  • Dispatchers.Main - coroutine will run in the Main Thread (also called UI Thread). There’s only one, so execute only lightweight tasks.
  • Dispatchers.IO - coroutine will run in a background thread from a shared pool of on-demand created threads. We use this to work with local database, networking or work with files.
  • Dispatchers.Default - for CPU intensive stasks such as sorting a large list.
  • Dispatchers.Unconfined - coroutine will run in the current thread, but if it’s suspended and resumed it will run on suspending function’s thread. This is used with GlobalScope. It’s not recommended for Android Development.

We may as well create our own executors and dispatchers. Most of the time, we should use Main and IO.

Coroutines Builders

The following snippet .launch{} is the coroutine builder. This are extension functions of coroutine scopes.
There are four main builders:

  • launch - this launches a new coroutine without blocking the current thread. It returns an instance of Job, which can be used as a reference to the coroutine or to cancel it. We use this for coroutines that doesn’t have any result as the return value.
  • async - it’s used to get a result value. It allows us to launch coroutines in parallel without blocking the current thread. This returns an instance of Deferred<T>. We need to invoke await() to get the value. This are the ones we use most in Android Development.
  • produce - Is for coroutines which produces a Stream of elements. Returns an instance of ReceiveChannel.
  • runblocking - Used mostly for testing. It will block the thread while the coroutine is executing. It returns a result of type T.

Switch the Thread of a Coroutine

In Android, we cannot directly call to a view component running in the UI Thread from a Background Thread. This will crash with a CalledFromWrongThreadException.

THIS WON’T WORK

btnDownload.setOnClickListener {
	CoroutineScope(Dispatchers.IO).launch {
		downloadData()
	}
}

// executed in a coroutine
private fun downloadData() {
	for (i in 1..20000) {
		// THIS WON'T WORK.
		textView.text = "Downloading user $i in ${Thread.currentThread().name}"
	}
}

Only the original thread that created a view hierarchy can touch its views, therefore we have to call views from the UI Thread. We add withContext() and suspend to the function’s args, as withContext is a suspending function. We cannot call a suspending function from a normal function.

// executed in a coroutine
private suspend fun downloadData() {
	withContext(Dispatchers.Main) {
		for (i in 1..20000) {
			textView.text = "Downloading user $i in ${Thread.currentThread().name}"
		}
	}
}

Suspending Functions

Whenever a coroutine is suspended, the current stack frame of ther function is copied and saved into memory. When the function resumes after completing its task, the stack frame is copied back from where it was saved and starts running again.

Other libraries such as room and retrofit, also provide suspending functions support.

These are some of those suspending functions: withContext, withTimeout, withTimeoutOrNull, join, delay. If we’re going to call them from our own functions, we have to mark our own functions as suspend fun. A suspending function can only be called from a coroutine block or from another suspending function. suspending means a function with a heavy long running task.

A suspending function doesn’t block a thread. It only suspends the coroutine itself. That thread is returned to the pool while the coroutine is waiting, and when the waiting is done the coroutine resumes on a free thread in the pool.

Async Builder and Await

Writing code to download data in parallel and combine them at the end is called parallel decomposition. To do it, we need to use async coroutine builder. This returns and instance of Deferred. We can use it by invoking its await function.

fun asyncTest() {
	CoroutineScope(Main).launch {
		val stock1 = async(IO) {getStock1()}
		val stock2 = async(IO) {getStock2()}
		val total = stock1.await() + stock2.await()
	}
}

private suspend fun getStock1() {
	// ...
}

Since this coroutine runs on the Main Thread, we could add a Toast message or interact with the view.

Unstructured Concurrency

Unstructured concurrency does not guarantee to complete all the tasks of the suspending function, before it returns. There’re situations we need to launch multiple coroutines concurrently in a suspending function and get some result returned from the function.

This is the wrong way to do it!.

class UserDataManager {
	suspend fun getTotalUserCount(): Int {
		var count = 0
		
		CoroutineScope(Dispatchers.IO).launch {
			delay(1000)
			count = 50
		}
		
		return count
	}
}

This will return 0 instead of 50, as it doesn’t wait for the async task. The child coroutines can be still running, even after the completion of the parent coroutine.

Structured Concurrency

Structured concurrency guarantees to complete all the work started by coroutines within the child scope before the return of the suspending function. It helps us to keep track of tasks we started and to cancel them when needed.

All previous problems can be solved using the suspending function coroutineScope. It allows us to create a child scope, within a given coroutine scope. This guarantees the completion of the tasks when the suspending function returns.

class UserDataManager {
	var count = 0
	lateinit var deferred: Deferred<Int>
	
	suspend fun getTotalUserCount(): Int {
		coroutineScope {
			launch {
				delay(1000)
				count = 50
			}
		}
	
		deferred = async(IO) {
			delay(3000)
			return@async 70
		}
		
		return count + deferred.await()
	}
}

Coroutines patterns with ViewModel and LiveData

For either ViewModelScope and LifeCycleScope we need to implement the following libraries.

def arch_version = '2.2.0-alpha04'
implementation 'androidx.lifecycle:lifecycle-extensions:$arch_version'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:$arch_version'

ViewModelScope

We have a ViewModel class. We define the coroutine scope we want to use and the method where we call it from.

In Android everytime a ViewModel is cleared from memory, just before cleaning it invokes its onCleared() method.

Some of the coroutines we launch in a ViewModel have the potential to run even after the ViewModel is cleared from memory. It might run until app termination. If this is not intended, we will end up leaking memory. To avoid this, we need to clear the coroutine.

class MainActivityViewModel: ViewModel() {

	// allows us to control all coroutines launched in this scope
	private val myJob = Job()
	private val myScope = CoroutineScope(Dispatchers.IO + myJob)

	fun getUserData() {
		myScope.launch {
			// write code. 
		}
	}

	override fun onCleared() {
		super.onCleared()
		// this is useful to cancel the coroutines for a couple of ViewModels
		myJob.cancel()
	}

}

If we have 20 ViewModels, there’s a better alternative with viewModelScope as this may be a wasting of time. This is bounded to ViewModel’s lifecycle. It was created to automatically handle cancellation when the ViewModel’s onClear() is called.

viewModelScope is a CoroutineScope tied to a ViewModel. It needs the ktx dependency in gradle.

Now, for the previous ViewModel, we can simply use viewModelScope instead of myScope. Clearing will be done automatically if the ViewModel is cleared.

class MainActivityViewModel: ViewModel() {

	fun getUserData() {
		viewModelScope.launch {
			// write code. 
		}
	}

}

LifeCycleScope

A lifecycleScope is defined for each Lifecycle Object. All coroutines launched in this scope are canceled when the Lifecycle is destroyed. You can access the CoroutineScope either via lifecycle.coroutineScope or lifecycleOwner.lifecycleScope. This scope is for objects with a lifecycle, such as Activities and Fragments.

If it’s in an Activity or a Fragment, all the coroutines will be canceled when the onDestroy() method is called.

class MainActivity: AppCompatActivity() {

	override fun onCreate(savedInstanceState: Bundle?) {
		// ...
		
		lifecycleScope.launch {
			// do whatever with coroutines. 
		}
	}

}

Sometimes we might need to suspend execution of a code block, considering the current state of a lifecycle object. For that we have three additional builders.

  • lifecycleScope.launchWhenCreated - if you have long running operations which should happen only once during the lifecycle of the activity or fragment. This will be called when it’s created for the first time.
  • lifecycleScope.launchWhenStarted - this will launch when the activity or fragment started. For some operations we need our Fragment lifecycle to be at least started.
  • lifecycleScope.launchWhenResumed - this is the state in which the App interacts with the user.

LiveData Builder

Example Without LiveData Builder

We have the following data class with two properties.

data class User(val id: Int, val name: String)

Then we create a repository class.

class UserRepository {
	suspend fun getUsers(): List<User> {
		// implement
	}
}

We also need a ViewModel class. with a viewModelScope which launches a new coroutine. Then we have a withContext() which switches the thread of the coroutine to a background thread.

class MainActivityViewModel: ViewModel() {
	private var userRepository = UserRespository()
	var users: MutableLiveData<List<User>> = MutableLiveData()
	
	fun getUsers() {
		viewModelScope.launch {
			var result: List<User>? = null
			withContext(Dispatchers.IO) {
				result = userReppository.getUsers()
			}
			users.value = result
		}
	}
}

This is the MainActivity class. We invoke the previous function and observe the list of users.

override fun onCreate(savedInstanceState: Bundle?) {
	...
	
	mainActivityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
	mainActivityViewModel.getUsers()
	mainActivityViewModel.users.observe(this, Observer { myUsers ->
		myUsers.forEach {
			Log.i("MyTag", "name is ${it.name}")
		}
	})
}

With LiveData Builder

To use this we need to add the following dependency.

def arch_version = '2.2.0-alpha04'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$arch_version"

There’s a new block which will automatically execute when LiveData becomes active. It automatically decides when to stop executing and cancel the coroutines inside the building block considering the state of the lifecycle owner.

Inside the LiveData building block, you can use emit() function to set a value to LiveData.

This is the previous LiveModel. Remember long running tasks need to be executed in a background thread.

class MainActivityViewModel: ViewModel() {
	private var userRepository = UserRespository()
	
	var users = liveData(Distpatchers.IO) {
		val result = usersRepository.getUsers()
		emit(result)
	}
	
}

With this change, we don’t need to invoke the function in MainActivity anymore.

override fun onCreate(savedInstanceState: Bundle?) {
	...
	
	mainActivityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
	mainActivityViewModel.users.observe(this, Observer { myUsers ->
		myUsers.forEach {
			Log.i("MyTag", "name is ${it.name}")
		}
	})
}