1. Home
  2. Android
  3. Introduction to using Kotlin Coroutine Builders in Android

Using Kotlin Coroutine builders in Android

Priya Sindkar

Priya Sindkar

Native Android Developer. Avid Reader.

Last updated on

Table of Contents

What are Kotlin Coroutines?

Kotlin Coroutines came as a boon to me after struggling with asynchronous programming over 4 years of my career as an android developer. Kotlin Coroutines make writing asynchronous and non-blocking code easy to read and understand by eliminating the need to handle traditional callback pattern.

Kotlin Coroutine Builders in Android
Kotlin Coroutine Builders in Android

Coroutines are light-weight threads that do not block the main thread while providing the ability to call suspending functions on the main thread. Suspending functions are functions that have the ability to suspend the execution of current coroutine without blocking the current thread. These functions can resume execution at some point. The suspending feature of these functions helps us to write asynchronous code in a synchronous manner. It is also worthwhile to note that suspending functions are not asynchronous themselves. 

A suspending function can be written by adding a prefix to a function with the keyword “suspend” like so:

suspend fun doSomething() : String {
    return “Do something”
}

As we saw, suspending functions are the core concepts of Kotlin Coroutines. But how do we call these functions? Suspending functions can only be called from another suspending function or from a coroutine. The next question that immediately pops up in our mind is: how to start a coroutine? The answer is Coroutine Builders.

What are Coroutine Builders?

As the name suggests, coroutine builders are a way of creating coroutines. Since they are not suspending themselves, they can be called from non-suspending code or any other piece of code. They act as a link between the suspending and non-suspending parts of our code.

In the following sections, we will be discussing the following three Kotlin coroutine builders:

1. runBlocking

2. launch

3. async

runBlocking

As the name suggests, runBlocking is a coroutine builder that blocks the current thread until all tasks of the coroutine it creates, finish. So, why do we need runBlocking when we are clearly focusing to avoid blocking the main thread? 

We typically runBlocking to run tests on suspending functions. While running tests, we want to make sure not to finish the test while we are doing heavy work in test suspend functions.

We will see how easy it is to use them in the following section.

launch

launch is essentially a Kotlin coroutine builder that is “fire and forget”. This means that launch creates a new coroutine that won’t return any result to the caller. It also allows to start a coroutine in the background.You can create a new coroutine from launch as follows:

You can create a new coroutine from launch as follows:

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
    println("Starting main function..")

    GlobalScope.launch {
        println(doSomething())
    }
    runBlocking {
        delay(4000L) // make sure to keep the JVM alive in order to wait for doSomething() to execute
    }
}

suspend fun doSomething() : String {
    delay(3000L) // simulate long running task
    return "Did something that was 3 seconds long"
}

In the above code, we call the suspending function, doSomething() and after 3 seconds of delay we print the string: “Did something that is 3 seconds long”.

Note: “delay” in Kotlin, is a suspend function which delays a coroutine without blocking the current thread. It then resumes the coroutine after the specified time (In our case, 3 seconds or 3000 milliseconds).

async

We now move on to the best part of Kotlin Coroutines builders – async.

async is a coroutine builder which returns some value to the caller. async can be used to perform an asynchronous task which returns a value. This value in Kotlin terms is a Deferred<T> value.

We can simply use async to create a coroutine doing some heavy work like so:

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

fun main() {
    println("Hello, ")
    GlobalScope.async {
        doSomething()
    }
    
    runBlocking {
        delay(3000L) // only used to keep the JVM alive
    }
}
suspend fun doSomething() {
    delay(2000L)
    print("this is bad example for using async!!!")
}

The output for the above code would be, “Hello, this is a bad example for using async!!!”.

However, if doSomething() function doesn’t return anything, what is its use? 

I have created the above example precisely to point out this error. It is highly discouraged to use async when you have nothing to return. Using launch should suffice in this case, like so:

import kotlinx.coroutines.*

fun main() {
    println("Hello, ")
    GlobalScope.launch {
        doSomething()
    }

    runBlocking {
        delay(3000L) // only used to keep the JVM alive
    }
}
suspend fun doSomething() {
    delay(2000L)
    print("this is an example for using launch when async doesn't return anything!!!")
}

async-await

await() is a suspending function that is called upon the async builder to fetch the value of the Deferred object that is returned. The coroutine started by async will be suspended until the result is ready. When the result is ready, it is returned and the coroutine resumes.

Here’s an example of how to use await to fetch result from a coroutine:

import kotlinx.coroutines.*

val a = 10
val b = 20

fun main() {
    println("Gone to calculate sum of a & b")

    GlobalScope.launch {
        val result = async {
            calculateSum()
        }
        println("Sum of a & b is: ${result.await()}")
    }
    println("Carry on with some other task while the coroutine is waiting for a result...")
    runBlocking {
        delay(3000L) // keeping jvm alive till calculateSum is finished
    }
}

suspend fun calculateSum(): Int {
    delay(2000L) // simulate long running task
    return a + b
}

The output for the above code would be: 

Gone to calculate sum of a & b

Carry on with some other task…

Sum of a & b is: 30

Here’s how the above code works:

async will return a Deferred value as the result of calculateSums() by calling the suspending function await() on it. 

 await() will fetch us the result that calculateSum()/ async returns.

While calculateSum() is executing, the following happens:

async builder will suspend the coroutine (used for calculating sum). 

The execution of other tasks (print statement) continues. 

Once calculateSum() returns a value, the suspended coroutine will resume and print the calculated result.

Now, coming to the best part about using Kotlin Coroutines with async. We can use async coroutine builder to call multiple pieces of asynchronous code in parallel. We can then combine the results from both to display a combined result. This leads to lower latency and in extension, a superior user experience.

Let’s jump directly to an example to get the gist and later see how it works.

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

fun main() {
    runBlocking {
        val firstResult: Deferred<Boolean> = async {
            isFirstCriteriaMatch()
        }

        val secondResult: Deferred<Boolean> = async {
            isSecondCriteriaMatch()
        }
        
        if (firstResult.await() && secondResult.await()) {
            println("All criteria matched, go ahead!")
        } else {
            println("One of the criteria unmatched, sorry!")
        }
    }

}

suspend fun isFirstCriteriaMatch(): Boolean {
    delay(1000L) // simulate long running task
    return true
}

suspend fun isSecondCriteriaMatch(): Boolean {
    delay(1000L) // simulate long running task
    return false
}

In the example above, let us assume the functions isFirstCriteriaMatch()  and isSecondCriteriaMatch() perform long running tasks that return Boolean values. These Boolean results are used to decide what message to show to the user.  

If we go by the traditional callback pattern, we would follow the steps below:

1. Call isFirstCriteriaMatch() and save the result in a global variable

2. Call isSecondCriteriaMatch() and save the result in another global variable

3. Check results from both functions and display messages accordingly.

However, with async and await, we save considerable amount of time. We make asynchronous calls to both functions in parallel and wait for each function to return a value before showing the result. 

Thus, async suspends the coroutine while isFirstCriteriaMatch() and isSecondCriteriaMatch()  are executing and as soon as both return a value, the coroutine is resumed to print an appropriate message.

GlobalScope in coroutines and why to avoid it

If you haven’t noticed already, I have used GlobalScope in all my examples above to help ease the understanding of the internals of coroutine builders. Typically, use of GlobalScope is discouraged. Do you know why?

Quoting definition of Global Scope from Kotlin’s documentation– “Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely.”

GlobalScope creates global coroutines and these coroutines are not children of some specific scope. Hence, it is the developer’s responsibility to keep track of all such global coroutines and destroy them when their job is done. This burden of manually maintaining the lifetime of coroutines can lead to extra effort on the developer’s part. Also, if not handled properly, it can very well lead to memory leaks. Hence, use of GlobalScope should be avoided. However, as we saw, all coroutines must be created within some coroutine scope. Then, what is the recommended way?

As mentioned in Kotlin’s documentation for CoroutineScope – “The best ways to obtain a standalone instance of the scope are CoroutineScope and MainScope factory.”

  Example: private val mainScope = MainScope()

Every coroutine builder is an extension of coroutine scopes and inherits their context. Thus, every scoping function (coroutineScopes created) provides its own scope to its children and internally waits for each child coroutine to complete before marking itself finished. Thus, memory leaks are avoided internally.

Here is a wonderful explanation on why to avoid GlobalScope by Roman Elizarov (Team Lead for Kotlin):

https://medium.com/@elizarov/the-reason-to-avoid-globalscope-835337445abc

Also refer the official Kotlin documentation for detailed understanding of coroutine scopes and how they work at this link:

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html

Conclusion

Kotlin coroutines make it super easy to develop Android apps if you know how to use them in the right way. By writing asynchronous code with suspend functions and coroutine builders, one can avoid the confusing callback pattern. 

For example, consider the case when we have to call multiple APIs and do something after assessing the response from all of the APIs. Instead of a complex chain of calling APIs, we can have a simplified coroutines version. This will lead to a cleaner and self-explanatory code. 

Here a couple more articles to read on Kotlin coroutines!

1.     https://medium.com/mindorks/multiple-concurrent-asynchronous-calls-using-kotlin-coroutines-async-await-and-suspendcoroutine-26371fa49781 – For understanding how to avoid callback mechanism and still be able to achieve concurrency with asynchronous tasks

2.      https://proandroiddev.com/async-operations-with-kotlin-coroutines-part-1-c51cc581ad33 – This is a nice article that explains in detail about coroutines with great examples.

// Related Blogs

// Find jobs by category

You've got the vision, we help you create the best squad. Pick from our highly skilled lineup of the best independent engineers in the world.

Copyright @2024 Flexiple Inc