Kotlin

A Functional Way Of Handling Error In Kotlin With runCatching

Kotlin1.3 is here for almost a year and it brings a lot of changes. One of the features that got me excited is better error handling with runCatching and mapCatching. There are two things that the developer needs to do when the error happens:

  • You can ignore it.
  • Let the error grow, or do something to improve the world.

Let’s see a simple example of how we handle error traditionally.

fun main() { 
    try {
        val value = getRandomNumber()
        println("The random number we get is -> $value")
    } catch (e: Exception) {
        System.err.println("Error occurred -> ${e.message}")
    }
}

@Throws(Exception::class)
private fun getRandomNumber(): Int {
    val randomNumber = (1..20).shuffled().first()
    if (randomNumber % 2 == 0)
        return randomNumber
    else
        throw Exception("The random number is odd.")
}

There’s nothing wrong with the above code. We successfully caught the exception if the odd number generates or print the value if the even number comes.

But the try {...} catch(e: Exception){...} construct code looks out of style. Kotlin encourages the developers to write code in a functional style or chaining like we do when working with RxKotlin.

Error Handling In A Function Style

Now if we try to handle the exception with Single as a return type and process potential error in a functional style.

private fun getRandomNumber(): Single<Int> {
    ......
    ......
}

fun main() {
     getRandomNumber()
           .subscribe({ 
                println(it) 
           }, { 
                System.err.println(it.message) 
           })
}

The above code is written in function-style that is very different from direct programming style that we’ve seen above. The getRandomNumber method simply returns a Single<Int>. It does not do actually anything until the subscribe method is invoked (the result is typically cold).

Now if we see the above code it looks more idiomatic and more functional in nature. Since the Kotlin1.3 the standard provides the same functionality without any kind of extra dependency.

Error Handling In A Functional Style With runCatching

Before start seeing the example of runCatching let’s see the implementation of it first:

public inline fun <R> runCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

Nothing fancy happening here, runCatching executes the specified block inside the try {...} catch(e: Throwable){...} and returns its encapsulated result if the invocation was successful, or thrown exception if it is a failure. You can read more about the Result here on this link.

The following shows the example of exception handling in a functional way with runCatching.

@Throws(Exception::class)
private fun getRandomNumber(): Int {
    val randomNumber = (1..20).shuffled().first()
    if (randomNumber % 2 == 0)
        return randomNumber
    else
        throw Exception("The random number is odd.")
}

fun main() {
    runCatching {
        getRandomNumber()
    }.onSuccess {
        println(it)
    }.onFailure {
        System.err.println(it.message)
    }
}

If the getRandomNumber method throws an exception the onFailure will be called with a given exception. On the other hand, if it is completed successfully the onSuccess will be called.

Let’s see another example with parallel execution of multiple asynchronous operations that must capture the successful or failed execution of each individual number.

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async

suspend fun main()  {  
    val deferreds: List<Deferred<Int>> = List(10) {  // 1
        GlobalScope.async {   // 2
            if (it % 2 == 0)
                [email protected] it
            else
                throw Exception("The odd number comes inside the list")
        }
    }
    deferreds.map { // 3
        [email protected] runCatching { 
            [email protected] it.await()   // 4
        }
    }.forEach {
        it.onSuccess { value ->  // 5
            println(value)
        }.onFailure { t ->  // 6
            System.err.println(t.message)
        }
    }
}

Here’s what happening inside the above code.

  1. Creating a list of an integer from 0 to 9.
  2. Creates a coroutine and return it’s future result implementation of Deferred<Int> if the number is even and throws the Exception if the number is odd.
  3. Applying the map transformation on all List<Deferred>.
  4. Wrapping the Deferred await result inside the runCatching function. If something potentially fails the runCatching easily navigate the failure to downstream.
  5. Calling onSucess on all even number’s and print the standard output to console.
  6. Calling onFailure on all odd numbers and print out the error line.

You see we write functional-style error handling code when working with asynchronous programming without depending on 3rd party library.

Applying Transformation on Result<T> with mapCatching

Sometime we need to apply map transformation on single Result<T> before checking success or failure result.

abstract fun readBitmap(file :File) : Bitmap

fun readBitmapsWithCatching(files : List<File>) : List<Result<Bitmap>> {
    files.map {
        runCatching { file ->  
             [email protected] readBitmap(file)
        }
    }
}

readBitmapsWithCatching(files).map { result ->
    result.map { bitmap ->
         processBitmap(bitmap)
    }
}

In the above example consider I have a readBitmap function that received a file as an argument and returned the bitmap. The readBitmap method throws an exception if the file not found or bitmap not found. Normally our code will catch exception because we wrap the method inside the runCatching in readBitmapWithCatching function.

Here comes the interesting part which I wanna point out. You see after reading bitmaps from readBitmapWithCatching in the above code, we’re applying the map transformation and process the bitmap with processBitmap function. Now if the processBitmap throws an exception while processing bitmaps our application will crash. So, we need to write it using mapCatching instead of the map:

abstract fun readBitmap(file :File) : Bitmap

fun readBitmapsWithCatching(files : List<File>) : List<Result<Bitmap>> {
    files.map {
        runCatching { file ->  
             [email protected] readBitmap(file)
        }
    }
}

readBitmapsWithCatching(files).map { result ->
    result.mapCatching { bitmap ->
         processBitmap(bitmap)
    }
}

Further Reading

See the talk on Failed Function Execution on GitHub.

See the brief summary on Error Handling With runCatching on GitHub.

I hope you liked this article. If you have any feedback, feel free to comment below: I would be very happy to get suggestions and to see any different solution.

Thank you for being here and keep reading…

Write A Comment