Using Kotlin Coroutines in Android Development

Kayvan Kaseb
Software Development
9 min readDec 14, 2020
The picture is provided by Unsplash

Basically, asynchronous programming can be considered as an indispensable issue in advanced Android apps because as an Android developer you have to manage expensive and heavy tasks away from UI thread. Nowadays, Kotlin Coroutines introduce an advanced and efficient approach of concurrency design pattern, which can be used on Android to simplify asynchronous codes. As a matter of fact, this approach is much more simple, comprehensive, and robust in comparison with other approaches in Android. This article aims to discuss some key concepts, functions, and classes of Kotlin Coroutines in Android development.

Introduction and Overview

Fundamentally, asynchronous programming is a means of parallel programming in which a unit of work runs separately from the main application thread and notifies the calling thread of its completion, failure or progress. Thus, This means you can be able to handle some tasks in the background without avoiding UI freezes and annoying user experiences. Android offers some traditional asynchronous programming solutions such as Thread and AsyncTask. However, the problem has not completely solved yet. In other words, AsyncTask and Thread can easily produce memory leaks and overheads, and the problem of Callback Hell is one of the main negative consequences in traditional programming as well. Moreover, although RxKotlin or RxJava is one of the efficient mechanism in Android for concurrency as well as asynchronous programming, it takes a lot of time to get to know and use it efficiently in practice by developers. So, Google has studied accurately some best practices and some feedback for concurrency recently that are used by Android developers. Eventually, the best solution should follow three main principles in Google’s view, and they believe that Kotlin Coroutines provides these ideas as follows:

  1. Simple: It should be an easy solution to learn.
  2. Comprehensive: It should be a solution that covers all cases and solutions.
  3. Robust: It should be a built-in teasing story.

As it was mentioned, in Android 11, Google has marked Async class as deprecated. Therefore, developers have turned to use various custom solutions, such as RxJava, Thread, Executors, and ListenableFuture. However, with Kotlin, there is one more contender and that is Coroutines. Kotlin coroutines allow you to simplify asynchronous programming in your Android apps. This means instead of having a number of callbacks in your code, you can write your code in a more sequential way. Coroutines were added to Kotlin in version 1.3, and are established on some concepts from other programming languages. In one of Google’s recent surveys, Google has mentioned that over 50% of professional developers who use coroutines are seeing increased productivity. As a result, Google has recently announced that coroutines are the recommended solution for async code on Android.

Coroutines = Co + Routines

Co means cooperation and Routines means functions. This means that when functions cooperate with each other, we call it as coroutines.

There are some prominent reasons for this approach briefly as follows: 1. Structured concurrency 2. Non-blocking sequential code 3. Cancellation propagation 4. Natural exception handling. The following sections will consider most common concepts, classes, and functions, which are used in Kotlin coroutines.

CoroutineScope

Basically, scopes in Kotlin Coroutines are extremely useful because we have to cancel the background task as soon as the activity is destroyed. With structured concurrency in Kotlin, you can define a coroutineScope that starts one or more coroutines. A CoroutineScope keeps track of any coroutine you create by using launch or async, these are extension functions on CoroutineScope. You should create a CoroutineScope whenever you want to control the lifecycle of coroutines in a particular layer of your Android app. For instance, in this class that has a well-defined lifecycle, we can create a CoroutineScope by calling the CoroutineScope function as follows:

class SampleClass {   val scope = CoroutineScope()   fun loadExample() {     scope.launch {        //New coroutine..        //You can be able to call suspend functions...     }   }
}

By using this scope, we can create new coroutines. So, these coroutines will follow the scope lifecycle. Google has recommended to use launch to get into coroutine, which is opposed to async. In fact, you can start coroutines in one of these two ways: 1. launch: this starts a new coroutine and does not return the result to the caller. Any work that is considered as a fire and forget can be started using launch. 2. async : this starts a new coroutine and allows you to return a result with a suspend function that is called await. Also, by using await() (for a single coroutine) or awaitAll() (for multiple coroutines), you can be able to guarantee that these coroutines finish before returning from the function. In the above example, whenever the SampleClass instance requires to be cleaned up because it is going to be destroyed, you can cancel all ongoing work automatically. This means you can perform this task by calling cancel on the scope, which cancels and stops executing all active coroutines as follows:

class SampleClass {     val scope = CoroutineScope()     fun loadExample() {...}     fun cleanup()   {         scope.cancel()     }
}

viewModelScope and lifecycleScope

In fact, because creating and managing scopes manually might require some boilerplate, in Android, Google has provided KTX libraries that already provide a coroutine scope in certain lifecycle classes, such as viewModelScope for the ViewModel class and lifecycleScope for lifecycle owners. When using viewModelScope, the ViewModel class cancels the scope automatically for you in the ViewModel's onCleared() method.

class SampleViewModel : ViewModel() {     fun loadExample() {
viewModelScope.launch {
//New coroutine..
//You can call suspend function..
}
}
}

Any Coroutine that has been launched in the scope is automatically canceled when the ViewModel is cleared. Coroutines are helpful here for when you have work that requires to be accomplished, the ViewModel is alive. Similarly, you have lifecycleScope for lifecycle, for example, these two scopes that we have seen run the coroutines on the main thread, as the scope is configured with Dispatchers domain. To change where the Coroutines runs, you must use a coroutine dispatcher. In the following we are passing Dispatchers.IO to load the asset from an IO-optimized thread rule:

Class SampleActivity : AppCompatActivity() {  fun loadAsset() {
lifecycleScope.launch(Dispatchers.IO) {
//New coroutine...
//You can call suspend functions..
}
}
}

CoroutineContext

In short, a CoroutineContext is an indexed set of elements that define the behavior of a Coroutine. There are four elements you can use and combine in a CoroutineContext as follows:

1. CoroutineDispatcher: a CoroutineDispatcher dispatches work to the proper thread by using these ways:

Dispatchers.IO: Networking or reading and writing from files (any input and output)

Dispatchers.Default: For doing CPU-intensive work, such as sorting large lists, performing complex calculations.

Dispatchers.Main: For everything else, new UI or a non-blocking code. Also, it is the recommended dispatcher for performing UI-related events such as showing lists in a RecyclerView, updating view, and so on.

2. CoroutineExceptionHandler: Another element that can be added to the CoroutineContext is the CororoutineExceptionHandler, which is an optional that allows you to handle uncaught exceptions.

3. CoroutineName: It gives a name to the coroutine, which might be useful for debugging.

4. Job: It controls the lifecycle of the Coroutine, not only a coroutine, but also a CoroutineScope. Each coroutine that you create with launch or async returns a Job instance, which uniquely identifies the coroutine and handles its lifecycle.

For example:

class SampleClass {

val scope = CoroutineScope(Job() + Dispatchers.Main)

fun exampleMethod() {
// It starts a new coroutine within the scope..
scope.launch {
// New coroutine that can call suspend functions..
fetchDocs()
}
}

fun cleanUp() {
scope.cancel()
}
}

The CoroutineScope function takes a CoroutineContext as a parameter. When creating a CoroutineScope, you can pass in a job to control its lifecycle. However, if you do not perform it, the function will carry out it for you. So, now, you can cancel the scope itself as before by calling scope.cancel(), or you can cancel the job. Both options are functionally equivalent.

The significant point is that new Coroutines created by this scope will inherit its CoroutineContext and will overwrite the job element with a new instance of job.

Furthermore, you can combine multiple CoroutineContexts using the plus operator as the CoroutineContext is a set of elements. When combining them, a new CoroutineContext will be created, with the elements of the context on the right side of the plus overwriting those on the left. For new coroutines created within a scope, a new Job instance is assigned to the new coroutine, and the other CoroutineContext elements are inherited from the containing scope. Thus, you can override the inherited elements by passing a new CoroutineContext to the launch or async function. For instance:

class SampleClass {
val scope = CoroutineScope(Job() + Dispatchers.Main)

fun sampleMethod() {

val jobOne = scope.launch {
// New coroutine with CoroutineName = "coroutine" (default)
}

val jobTwo = scope.launch(Dispatchers.Default + "BackgroundCoroutine") {
//New coroutine with CoroutineName= "BackgroundCoroutine(overridden)
}
}
}

withContext()

To modify the CoroutineContext within a Coroutine, you should use withContext. That is a suspend function from the Coroutine’s library. As withContext() is itself a suspend function, any function that is included it can also be a suspend function. For example:

Class SampleClass {
val scope = CoroutineScope(… )
fun loadExample() { //New instance of Job created..
val job = scope.launch {
//Using Dispatchers.Main here..
withContext() {
//Using Dispatchers.Default here..
}
}
}
}

withContext() does not add extra overhead compared to an equivalent callback-based implementation. In other words, with coroutines, you can dispatch threads with fine-grained control. Because withContext() allows you control the thread pool of any line of code without introducing callbacks, you can apply it to very small functions like reading from a database or doing a network request.

SupervisorJob

Basically, Jobs take a significant role in coroutines. Besides, the job influences how errors are propagated. For instance, we create a CoroutineScope with a job, and the launch four new Coroutines. These Coroutines have parent that is the scope. If one of the children fails, the failure is propagated to their parent, which will send a cancellation signal to the rest of the children. Then, the other coroutines will get canceled. Then, the scope will get canceled too. So, they will propagate an exception up to their thread’s default exception handler. But you may not want this behavior in some cases, for example, in a UI-related scope. If a Coroutine fails, you might not want the UI scope to be canceled due to having a non-responsive UI. For those cases, you can use a SupervisorJob.

By using a SuperVisiorJob, the failure of a child does not affect other children. This means they will not get canceled. Also, a SupervisorJob will not propagate the exception either. Therefore, it will let the coroutine manage it. With a SupervisorJob, when a child fails, the rest of the children will not be affected; however, unhandled exceptions are still propagated. If you do not handle the exception in the coroutine, you will need a CoroutineExceptionHandler in the scope. Finally, to have even more control over this process when you are in a coroutine, you can build some scopes to logically that group other coroutines. This is mainly because the outer scope suspends and will not resume until all the Coroutines created within it complete. The supervisorScope inherits the CoroutineContext used in the outer coroutine and will overwrite the job element with a new job or SupervisorJob accordingly.

Benefits of using Kotlin Coroutines

All in all, there are some advantages can be mentioned for using Kotlin Coroutines in Android development as follows:

  1. Being lightweight: You can run many coroutines on a single thread because support for suspension, which does not block the thread where the coroutine is running. Furthermore, suspending saves memory over blocking as well as supporting a number of concurrent operations.

2. Having fewer memory leaks: By using structured concurrency to run operations within a scope you will face fewer memory leaks in your codes

3. Offering Built-in cancellation: Cancellation could be propagated automatically through the running coroutine hierarchy.

4. Providing Jetpack integration: Many Jetpack libraries like extensions that provide full coroutines support. Moreover, some libraries support their own coroutine scope, which you can use for structured concurrency.

In conclusion

A coroutine is a concurrency design pattern, which you can use in Android development to simplify code for executing asynchronous programs. Coroutines were added to Kotlin in version 1.3. The above sections of this essay considered most common concepts, classes, and functions, which are used in Kotlin coroutines based on Google resources.

--

--

Kayvan Kaseb
Software Development

Senior Android Developer, Technical Writer, Researcher, Artist, Founder of PURE SOFTWARE YAZILIM LİMİTED ŞİRKETİ https://www.linkedin.com/in/kayvan-kaseb