DEV Community

Hossain Khan
Hossain Khan

Posted on • Originally published at hossainkhan.Medium on

Kotlin coroutines error handling strategy — `runCatching` and `Result` class

Kotlin coroutines error handling strategy — runCatching and Result class

I am trying to learn Kotlin coroutines, and was trying to learn more about how to handle errors from suspended functions. One of the recommended way by Google is to create a “Result” class like the following:

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}
Enter fullscreen mode Exit fullscreen mode

This allows us to take advantage of Kotlin’s when like following:

when (result) {  
 is Result.Success<LoginResponse> -> // Happy path  
 else -> // Show error in UI  
}
Enter fullscreen mode Exit fullscreen mode

However, I have recently stumbled into Kotlin’s runCathing {} API that makes use of Result class available in standard lib since 1.3

Here I will try to explore how the native API can replace the recommended example in the Android Kotlin training guide for simple use cases.

Here is a basic idea of how runCatching {} can be used from Android ViewModel.

Based on Kotlin standard lib doc, you can use runCatching { } in 2 different ways. I will focus on one of them, since the concept for other one is similar.

To handle a function that may throw an exception in coroutines or regular function use this:

val statusResult: Result<String> = runCatching {
    // function that may throw exception that needs to be handled
    repository.userStatusNetworkRequest(username)
}.onSuccess { status: String -> 
  println("User status is: $status")
}.onFailure { error: Throwable ->  
  println("Go network error: ${error.message}")
}

// Assuming following supposed* long running network API
suspend fun userStatusNetworkRequest(username: String) = "ACTIVE"
Enter fullscreen mode Exit fullscreen mode

Notice the ‘Result’ returned from the runCatching this is where the power comes in to write semantic code to handle errors.

The onSuccess and onFailrue callback is part of Result class that allows you to easily handle both cases.

How to handle Exceptions

In addition to nice callbacks, the Result class provides multiple ways to recover from the error and provide a default value or fallback options.

  1. Using getOrDefault() and getOrNull() API
val status: String = statusResult.getOrDefault("STATUS_UNKNOWN")

// Or if nullable data is acceptable use:
val status: String? = statusResult.getOrNull()
Enter fullscreen mode Exit fullscreen mode

Since the onSuccess and onFailure returns Result you can chain most of these API calls like following

val status: String = runCatching { 
  repository.userStatusNetworkRequest("username")
}
.onSuccess {}
.onFailure {}
.getOrDefault("STATUS_UNKNOWN")
Enter fullscreen mode Exit fullscreen mode

2. Using recover { } API

The recover API allows you to handle the error and recover from there with a fallback value of the same data type. See the following example.

val status: Result<String> = runCatching { 
  repository.userStatusNetworkRequest("username")
}
.onSuccess {}
.onFailure {}
.recover { error: Throwable -> "STATUS_UNKNOWN" }  

println(status.isSuccess) // Prints "true" when error is received
Enter fullscreen mode Exit fullscreen mode

3. Using fold {} API to map data

The fold extension function allows you to map the error to a different data type you wish. In this example, I kept the user status as String.

val status: String = runCatching {
  repository.userStatusNetworkRequest("username")
} 
.onSuccess {} 
.onFailure {} 
.fold(
    onSuccess = { status: String -> status },
    onFailure = { error: Throwable -> "STATUS_UNKNOWN" } 
)
Enter fullscreen mode Exit fullscreen mode

Aside from these, there are some additional useful functions and extension functions for Result , take a look at official documentation for more APIs.

I hope this was useful or a new discovery for you as it was for me 😊

Top comments (0)