KOTLIN COROUTINES

Shivram Shrestha
7 min readSep 12, 2023

--

Coroutines are a features in kotlin programming languages allows to write asynchronous code in a sequential and more readable manner. They were introduced as an experimental feature in kotlin 1.1 and it became stable later in kotlin 1.3. Coroutines work by suspending the execution of a function at a certain point and resuming it later. This will allows to write code that can run concurrently with other code, without blocking the main thread.

Before going to the deep in coroutine let’s see about what is asynchronous and synchronous.

Asynchronous

Asynchronous refers where the task might take some time to complete (like fetching data from internet). The program does not pause and wait for the task to complete. Instead it continue to execute other task or handling events. This helps us to keep everything moving smoothly and efficiently.

Synchronous

On the other hand, the synchronous refers where the task are executed one after another in a sequential manner and the program waits for the task to complete before moving on to the next task. If a task takes a longer time to complete (such as reading a large file) the program will be unresponsive until the task is finished.

Suspend function

A suspend function is a type of function that can be paused and resumed without blocking the current thread. This feature is part of Kotlin’s coroutine framework, which enables you to write asynchronous code in a more sequential and readable manner.

suspend fun doSomething() {
// Do something useful here
}

What is Coroutines?

Coroutines are always useful for writing asynchronous task more efficiently and avoid nested callbacks which makes our code more readable and easy to maintain. To declare a coroutine in android you must first include AndroidX that support library in your project and add the dependency in your build.gradle file.

dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:$latest_dependency '
}

Then in your class, you have to import CoroutineScope class and create an instance to this class. To create a coroutine, you must use the launch or aync method of CoroutineScope. The launch method starts a new coroutine that doesn't return a result, while the async method also used to create a new coroutine but it returns a result.

Example for launch method

class MyViewModel : ViewModel() {

fun fetchDataFromApi() {
viewModelScope.launch {
// This code runs in a background thread
}
}
}

Example for async method

class MyViewModel : ViewModel() {
fun fetchDataFromApi() : Deferred<Data>{
return viewModelScope.async {
// Get the data and returns the result
}
}
}

As we can see above in both examples, an instance of CoroutineScope is created and the launch or async method is used to create the coroutine. Both the function takes lambda code block as an argument that contains logic of the coroutine. We will see more details about the launch and async .

What is CoroutineScope?

The CoroutineScope defines the lifetime of coroutines. In android you often use ViewModel and the lifecycleScope provides the android.lifecycle.lifecycle-runtime.ktx” library to manage the lifecycle of an android component (like an activity or fragment).

Types of coroutineScope:

There are several types of CoroutineScope that we can create coroutines in Android they are:

a) GlobalScope

b) viewModelScope

c) lifecycleScope

a) GlobalScope:

The globalScope is not tied into any specific lifecycle and it would exist throughout the entire application. GlobalScope are not automatically canceled when an android component (Activity or Fragment ) lifecycle ends, so it can leads to memory leaks if it is not managed properly.


GlobalScope.launch {
// Coroutine code
println("Running in GlobalScope")
}

b) viewModelScope:

The viewModelScope is an extension property provided by the androidx.lifecycle:lifecycle-viewmodel-ktx library in Kotlin for Android. It is used to create coroutines associated with the ViewModel and automatically cancels all its coroutines when the ViewModel is cleared. It is useful for performing asynchronous tasks that are related to the ViewModel and its lifecycle, allowing you to write asynchronous code that is tightly integrated with the Android architecture components and doesn't depend on the lifetime of a particular Activity or Fragment.


class MyViewModel : ViewModel() {

fun performTask() {
viewModelScope.launch {
// Coroutine code
println("Running in ViewModelScope")
}
}
}

c) lifecycleScope:

It is a lifecycle Owner (such as Activity or Fragment) that can be used to create coroutines and it is tied to the lifecycle of a LifecycleOwner. Any coroutine launched in this scope will be canceled when the LifecycleOwner is destroyed.

class MyActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Get the lifecycle scope for this activity
val lifecycleScope = lifecycleScope

// Launch a coroutine that fetches data from the network
lifecycleScope.launch {
val data = fetchDataFromNetwork()
// Do something with the data
}
}
}

Dispatchers

It will determine which thread the coroutine will be executed. Dispatchers provides a way to switch between different thread without directly managing the thread manually. In kotlin’s it provides several built-in dispatchers that you can use to control the execution context of coroutines. some of the commonly used dispatchers include :

  1. Dispatchers.Main

It is used for UI-related work on the main (UI) thread and it is useful for updating the UI from a coroutine.

2. Dispatchers.IO

It is used to performing I/O related task like network request or file operation.

3. Dispatchers.Default

It is a shared dispatcher that uses a pool of threads. When you launch a coroutine on Dispatchers.Default, the coroutine will be assigned to one of the threads in the pool, this will be choosen automatically by the kotlin runtime. If the kotlin runtime cannot find a free thread, the coroutine will be placed in the queue and will wait for a thread to become available. It’s a good idea to choose Dispatchers.Default for CPU-intensive operations.

4. Dispatchers.Unconfined

It allows coroutines to run on any thread. It is often used for testing and debugging.

Coroutine Builder

Coroutine Builders are functions that help create and start coroutines. They can be called from regular functions, but many of them are suspending functions that allow for asynchronous and non-blocking execution within coroutines. Some of the commonly used Coroutine Builder are:

  1. launch

This builders starts a new coroutine that is fire and forget and does not return anything

fun main() {
launch {
// Do something in a coroutine
}
}

2. async

You use async to start a new coroutine that performs some asynchronous computation or task. The async builder immediately returns a Deferred object, which represents the result of that computation. You can think of it as a promise to hold the result. You can continue with other tasks or coroutines while the original coroutine started by async is running in the background. When you need the result of the computation, you can use the await() function on the Deferred object. This function suspends the current coroutine until the result is available, but it does not block the calling thread.

Using async and Deferred allows you to perform concurrent asynchronous operations and retrieve their results in a non-blocking manner, making it a powerful tool for writing efficient and responsive asynchronous code.


val deferred = async {
// Simulate some work in a coroutine and return a value
delay(1000) // Simulate work (e.g., network request or computation)
"Hello, World!"
}

// Do something else while the async task is running
println("Do something else while waiting...")

// When you need the result, use await() to retrieve it.
val value = deferred.await()

println("Result 1: $value")

3. runBlocking

This builders blocks the current thread until the coroutine completes.


runBlocking {
// Code inside this block runs in a coroutine
println("Coroutine is running...")
delay(1000) // Simulate some work (e.g., a delay)
println("Coroutine has completed.")
}

// Code here runs after the coroutine has completed
println("Main function continues...")

Exception Handling in Coroutines

In Kotlin coroutines, you can handle exceptions using several mechanisms to ensure that your code remains robust and can recover from errors. Here are some of the key exception handling mechanisms in Kotlin coroutines:

  1. try-catch Blocks:

You can use traditional try-catch blocks to catch exceptions that occur within a coroutine. Here’s an example:

import kotlinx.coroutines.*

fun main() = runBlocking {
try {
val result = withContext(Dispatchers.IO) {
// Perform some potentially throwing operation
throw IllegalStateException("An error occurred")
}
println("Result: $result")
} catch (e: Exception) {
println("Caught an exception: ${e.message}")
}
}

2. CoroutineExceptionHandler:

You can set a custom CoroutineExceptionHandler for a specific scope or for the entire coroutine context to handle exceptions. This allows you to centralize exception handling for multiple coroutines. Here's an example:

import kotlinx.coroutines.*

fun main() = runBlocking {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Caught an exception: ${exception.message}")
}

val job = GlobalScope.launch(exceptionHandler) {
// Perform some potentially throwing operation
throw IllegalStateException("An error occurred")
}

job.join()
}

3. SupervisorJob:

When using a SupervisorJob, exceptions in one child coroutine do not affect other child coroutines. You can handle exceptions individually for each child coroutine. Example:

import kotlinx.coroutines.*

fun main() = runBlocking {
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Default + supervisorJob)

val job1 = scope.launch {
// Child coroutine 1
throw IllegalStateException("Error in job1")
}

val job2 = scope.launch {
// Child coroutine 2
throw ArithmeticException("Error in job2")
}

job1.join()
job2.join()
}

--

--

Shivram Shrestha

Software Engineer. 5 years of experience in Mobile application development and Web technologies .