quora How to use Kotlin Coroutines in Android • QuickBlox

Q-Consultation for every industry

Securely hold virtual meetings and video conferences

Learn More>

Want to learn more about our products and services?

Speak to us now

How to use Kotlin Coroutines in Android

Vitaliy Dovbnya
8 Dec 2022
Kotlin Coroutines

To improve efficiency when creating mobile applications many Android developers are discovering the benefits of using coroutines. Variously described as a concurrency design pattern, or an instance of suspendable computation that is conceptually similar to a thread, coroutines make possible asynchronous or non-blocking programming. Using coroutines can enhance developer productivity allowing them to write sequential code that is simple and easy to read.

Read on to learn more about how you can use coroutines in Android.

Find out more about How to implement push notifications in your android app

What is Asynchronous Programming?

Every Android developer has come across synchronous and asynchronous approaches to mobile app development. Both forms can be performed on one or more threads.

The difference is that in synchronous programming, developers execute one task at a time. When one task is completed, the next task begins. In this approach, it is impossible to stop the execution of a task in order to perform another task in between.

With asynchronous programming, several tasks are executed simultaneously. The thread that started the execution of a task, can suspend the execution of that task, and in the meantime start the execution of another.

Find out more about How to Build an Android Chat App with Kotlin using QuickBlox SDK

What Are Kotlin Coroutines?

Asynchronous programming is an important part of development. There are several asynchronous programming approaches in Android, including Coroutines. The coroutine is less resource costly than JVM threads. You can read more about it in the official documentation of Kotlin Coroutines.

Coroutines allow you to perform tasks without blocking the main thread. The main thread is a separate thread that handles updates, clicks, and other UI callbacks. Therefore, you need to avoid blocking the main thread to ensure that your application works correctly and efficiently.

Take for example, the scenario of needing to download data from a server or database and displaying it on the screen. The challenge is that loading data is a synchronous task and takes time to complete. If you run it on the main thread, the UI will block until the task is completed. Kotlin solves this problem by providing support for coroutines at the language level.

In the following I will break down the main features of coroutines to explain the best way to work with them.

Find out more about How to Launch Android Chat and WebRTC Video Conferencing Samples

Suspend function

Suspend keyword — marks functions that can be started, suspended, and resumed from a coroutine. One of the most important things to remember about suspend functions is that they can only be called from a coroutine or other suspend function.


suspend fun loadUser(): User {
   return // for example, some long task for retrieve user from REST or database
}

In this example, we have marked function loadUser() in as suspend.

CoroutineScope

A coroutine can only be performed in a certain area of the coroutine CoroutineScope.

The scope of coroutines represents the space within which coroutines execute. It has a certain life cycle and controls the life cycle of coroutines created inside it and can take a CoroutineContext as a parameter.

CoroutineContext

CoroutineContext is an interface for a collection of Elements. We can say that this is a set of attributes that configures the coroutine.

Dispatchers

Dispatcher is one element of the CoroutineContext. The coroutine manager determines which thread or threads will be used to execute the coroutine.

Dispatchers.Default – If the dispatcher type is not explicitly specified, the default is used. This type is used for complex calculations, which require heavy consumption of CPU resources.

launch(Dispatchers.Default) {
    // some calculations
}

Dispatchers.IO – designed to perform I/O operations such as file operations or network requests.

launch(Dispatchers.IO) {
     // some network request
}

Dispatchers.Main – designed to work in the main thread (to work with the UI).

launch(Dispatchers.Main) {
    // some logic for UI
}

Dispatchers.Unconfined – the coroutine is not running on a specific thread or thread pool. It runs on the current thread before the first pause. After the resumption of work, the coroutine continues to work in one of the threads.

launch(Dispatchers.Unconfined) {
    // do something, no matter in what thread
}

Method launch()

launch() – is a coroutine builder. It launches a new coroutine. The code inside the coroutine is executed sequentially. It returns a reference to the coroutine as a Job. It is generally used when we do not need to return a result from a coroutine and when we need to execute it simultaneously with other code.

In this example, we have launched a task program in which some tasks will be performed.

fun someMethod() {
    val scope = CoroutineScope(Dispatchers.IO)

     val job: Job = scope.launch {
          // some long task
     }   
}

Job

Job – is an element of CoroutineContext that allows you to manage the life cycle of a coroutine.

With the help of Job, you can stop the execution of the coroutine. It can also be used to create a hierarchy of coroutines. That is, run other coroutines inside the coroutine and manage their life cycle. If there is an error or an exception in the child coroutine, then the parent coroutine will cancel with all the child ones.

Method withContext()

withContext() – is a function that allows us to create a new coroutine.
We can pass a dispatcher to this function so that the performance of the block will happen on a thread from the passed dispatcher. After executing a task in a block, the control returns to the previous dispatcher.

If there are several withContext blocks inside the parent block, each of them is suspended by the parent thread, and will be executed one after the other in sequence.

In this example, we start the coroutine with the Dispatchers. Main to run on the main thread. The withContext() method stops the parent coroutine and launches the child in with its Dispatchers.IO. The new coroutine executes the task in another thread.

After the execution, the child program will start suspended parent coroutine with Dispatchers.Main and method showUserInfo() will perform in main thread

private fun switchDispatcher() {
   val scope = CoroutineScope(Dispatchers.Main)

   scope.launch {
       val user: User = withContext(Dispatchers.IO) {
          // some long task for retrieve user from REST or database
          loadUser()
       }
       showUserInfo(user)
   }
}

private fun showUserInfo(user: User) {
    // work in the main thread, displaying data on the screen
}

Method async()

async() – is a coroutine builder. It launches a new coroutine just like
launch().

This function is used when you need to get some result from a coroutine. It returns a Deferred object that is waiting for the result of the coroutine. Deferred is inherited from the Job interface, so all the functionality of the Job interface is available to it.

The await() function is used to get the result from the Deferred object. It waits until the result is received.

suspend fun loadFile() {
    val scope = CoroutineScope(Dispatchers.IO)

    val deferred: Deferred = scope.async {
         // for example, some long task for load file from REST
    }

    val file: file = deferred.await();
}

We can also use async to run multiple coroutines that will run in parallel.

suspend fun loadTwoFiles() {
   val scope = CoroutineScope(Dispatchers.IO)

   val firstDeferred: Deferred = scope.async {
       // for example, some long task for load file from REST
   }
   val secondDeferred: Deferred = scope.async {
       // for example, some long task for load file from REST
   }
 
  val firstFile: File = firstDeferred.await()
  val seconbFile: File = secondDeferred.await()
}

This example launches two coroutines, each of which loads the files.

Coroutines return a Deferred object. Accordingly, calling the await() method on this object will return a File object. In this case, all two coroutines will be launched simultaneously and will execute asynchronously.

If there is an error or exception in one of the coroutines, then both coroutines will be left.

Handle exception

The coroutines use the well-known block try/catch to handle exceptions. The try block contains the code that can throw the exception, and the catch block is used to handle the exception. In the example below, if an exception occurs while executing some task, we can handle it in a block catch. This example uses coroutine builder – launch()

fun handleExcWithLaunch() {
    val scope = CoroutineScope(Dispatchers.IO)

    scope.launch {
        try {
            // some long task 
        } catch (exception: Exception) {
            println("Handle Exception: $exception")
        }
    }
}

For coroutine builder – async() also uses block tracks for exception handling. The difference is that we wrap in a try/catch block not a suspended function but an object Deferred

fun handleExcWithAsync() {
   val scope = CoroutineScope(Dispatchers.IO)

   val deferred: Deferred = scope.async {
       // some long task for retrieve user from REST or database
   }

   try {
       val user = deferred.await()
 } catch (exception: Exception) {
       println("Handle Exception: $exception")
   }
}

Consider the following example. The coroutine was launched in the coroutine with the try/catch block in which we will independently throw exceptions to test.

fun handleExcWithLaunch() {
   val scope = CoroutineScope(Dispatchers.IO)

   scope.launch {
       try {
           launch {
               throw RuntimeException("Thrown RuntimeException")
           }
       } catch (exception: Exception) {
           println("Handle Exception: $exception")
       }
   }
}

In this case, the exception will not be handled and the application will crash. The reason for this is that the internal coroutine exits exclusively, and the coroutine fails. If the coroutine does not handle exceptions itself with a try/catch block, the exception is not thrown and thus cannot be handled by the outer try/catch.

Instead, the exception propagates through the Job hierarchy and can be handled by a CoroutineExceptionHandler.

CoroutineExceptionHandler

CoroutineExceptionHandler is the ContextElement for handling uncaught exceptions. We can also use it to handle the exception in child coroutines.

private val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, exception ->
    println("Handle Exception: $exception")
}

private fun handleExcWithHandler() {
   val scope = CoroutineScope(Dispatchers.IO)

   scope.launch(coroutineExceptionHandler) {
       launch(Dispatchers.IO) {
           try {
               throw RuntimeException("Thrown RuntimeException")
           } catch (exception: Exception) {
               println("Handle Exception: $exception")
           }
       }
   }
}

Find out more about How to use GitHub with Android Studio

Conclusion

Asynchrony programming provides an efficient way to engage in app development work because you do not have to block the main thread of the application during the execution of time-consuming tasks.

In this article, we looked at how to use Kotlin coroutines to execute tasks asynchronously. Coroutines simplify asynchronous programming by leaving all the complications inside libraries. They also allow you to write sequential code that is more understandable and readable. What’s more, coroutines help to avoid the so-called “callback hell”, which is formed with callbacks when you need to perform tasks sequentially. Coroutines are a powerful and convenient tool with which you can create fast and high-quality mobile applications.

Interested in Android mobile app development? Find out more about Android SDKs and code samples.

Read More

Ready to get started?

QUICKBLOX
QuickBlox post-box