Kotlin Coroutines By Example (Exception Handling, Delay, Timeout & More)


Kotlin Coroutine Example

Introduction

Coroutines are a simple way to implement asynchronous and non-blocking code. It's implemented using suspending functions at the language level and with the help of the kotlinx.coroutines library.

Coroutine scopes and builders are used to define Coroutines.

To better understand Kotlin Coroutine concepts such as suspending functions, builders, scopes and contexts, you can follow this previously posted article.

Execute code in the background and continue

import kotlinx.coroutines.*

fun main() {
    println("The main program is started")
    GlobalScope.launch {
        println("Background processing started")
        delay(500L)
        println("Background processing finished")
    }
    println("The main program continues")
    runBlocking {
        delay(1000L)
        println("The main program is finished")
    }
}

Execute code in the background and continue

Concurrent code execution

Launch multiple computations in parallel and wait for them to finish

import kotlinx.coroutines.*
import java.text.SimpleDateFormat
import java.util.*

fun main() = runBlocking {
    val deferred1 = async { computation1() }
    val deferred2 = async { computation2() }
    printCurrentTime("Awaiting computations...")
    val result = deferred1.await() + deferred2.await()
    printCurrentTime("The result is $result")
}

suspend fun computation1(): Int {
    delay(1000L) // simulated computation
    printCurrentTime("Computation1 finished")
    return 131
}

suspend fun computation2(): Int {
    delay(2000L)
    printCurrentTime("Computation2 finished")
    return 9
}

fun printCurrentTime(message: String) {
    val time = (SimpleDateFormat("hh:mm:ss")).format(Date())
    println("[$time] $message")
}

Concurrent code execution

Cancelling a coroutine execution

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        // Emulate some batch processing
        repeat(30) { i ->
            println("Processing $i ...")
            delay(300L)
        }
    }
    delay(1000L)
    println("main: The user requests the cancellation of the processing")
    job.cancelAndJoin() // cancel the job and wait for it's completion
    println("main: The batch processing is cancelled")
}

Cancelling a coroutine execution

Execution timeout

Run a given block of code inside a coroutine with a specified timeout.

Throw exception on timeout

A TimeoutCancellationException is thrown if the timeout is exceeded.

import kotlinx.coroutines.*

fun main() = runBlocking {
    withTimeout(1000L) {
        repeat(30) { i ->
            println("Processing $i ...")
            delay(300L)
        }
    }
}

Throw exception on timeout

Return null on timeout

import kotlinx.coroutines.*

fun main() = runBlocking {
    val status = withTimeoutOrNull(1000L) {
        repeat(30) { i ->
            println("Processing $i ...")
            delay(300L)
        }
        "Finished"
    }
    println("The processing return status is: $status")
}

Return null on timeout

Exception handling

When using launch

By default, the exceptions thrown inside a coroutine started with launch don't require to be handled and are printed instead to the console. They are treated as uncaught exceptions.

However, they still can be handled by using a CoroutineExceptionHandler.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("$exception handled !")
    }
    val job = GlobalScope.launch(handler) {
        throw UnsupportedOperationException()
    }
    job.join()
}

When using async

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred = GlobalScope.async {
        // some business logic
        throw UnsupportedOperationException()
    }
    try {
        deferred.await() // The exception is thrown here
        println("Won't be printed")
    } catch (e: UnsupportedOperationException) {
        println("UnsupportedOperationException handled !")
    }
}

Kotlin Coroutines Exception handling

Android usage

Setup

To be able to launch coroutines with the Dispatchers.Main context, you have to add the kotlinx-coroutines-android dependency to your project. For example, when using Gradle, add the following line to your app/build.gradle file inside your dependencies:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1"

Examples

Coroutines are commonly used on View Models to fetch data from a database or from the Internet.

import androidx.lifecycle.*
import kotlinx.coroutines.*

// Android View Model (holds data to be displayed in a view)
class MyViewModel : ViewModel() {
    private val _properties = MutableLiveData<List<MyData>>()
    val properties: LiveData<List<MyData>>
        get() = _properties

    // To be able to cancel launched coroutines (if any)
    private val job = Job()

    // The coroutine runs using the Main (UI) dispatcher
    private val coroutineScope = CoroutineScope(job + Dispatchers.Main)

    init {
        coroutineScope.launch {
            val deferred: Deferred<List<MyData>> = MyService.getPropsAsync()
            _properties.value = deferred.await()
        }
    }

    // Cancel the job when the view model is destroyed
    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}

Soufiane Sakhi is an AWS Certified Solutions Architect – Associate and a professional full stack developer.