티스토리 뷰

Kotlin

Kotlin's Result sealed class

Kaboomba 2022. 2. 18. 05:15

It's cleaner and easier to handle not to throw Exception often in Kotlin. Particulary when we call Http call, we are like to use Coroutines. Coroutine has complicating cancellation and exception handling mechanism and it might not work well in caese if you use the same apporach to Java by throwing Exceptoin. Coroutine propagates Cancellation Exception to top most coroutine context and this makes error and cancellation handling not intuitive. Good option is that not throwing exception and we can wrap it in Result class instead.

 

Result class became public 1.3 but I didn't release until today. (I really wanted the class public but was not avaiable when I started my project and created my own and forgot about it)

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

 // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/

We can use Result class like below.

// Retrofit
suspend fun getUsers(): List<User>

suspend fun getUsers(): Result<List<User>> {
   try {
      Result.success(api.getUsers)
   } catch(e: Exception) [
      Result.failuer(e)
   }
}

when (val result = getUsers()) {
    is Result.Success<List<User>> -> {}
    else -> {}
}

 

We will repeat try catch if we need to catch exceptions. Kotlin already has extension function to generalise try catch.

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its encapsulated result if invocation was successful,
 * catching any [Throwable] exception that was thrown from the [block] function execution and encapsulating it as a failure.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

I didn't relealised this has one flaw until recently. We captured all exceptions and wrapped in Result. As I mementioned earlier, Coroutines needs to handle cancellation and should propagate CancellationException to parent context. But we are capturing CancellationException as well but not rethrowing.  Let's add new extension function.

public inline fun <T, R> T.runCatchingOrThrowCancellation(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        if (e is CancellationException) {
           throw e
        }
        Result.failure(e)
    }
}

 

This looks fine but if we have a code to show progress bar while calling runCatching and hide when it's finished. Progressbar won't stop if we encounter CancellationException in the case. So we need to signal about Cancellable to outside.

viewModelScope.launch {
    runCatchingOrThrowCancellation { repository.getUsers() }
        .onSuccess { ... }
        .onFailure { ... }
}

This looks fine but there might have edge case if we have a code to show progress bar while calling runCatching and hide when it's finished. Progressbar won't stop if we encounter CancellationException in the case. So, if you have this case, you might need to handle it in your ViewModel.

val handler = CoroutineExceptionHandler { _, exception -> 
    liveData.postValue(LoadingState(isLoading = false)
}

viewModelScope.launch(handler) {
    runCatchingOrThrowCancellation { repository.getUsers() }
        .onSuccess { ... }
        .onFailure { ... }
}

 

'Kotlin' 카테고리의 다른 글

SharedPreferences Property Delegate  (0) 2024.05.01
Kotln, Modern Programming Lauage  (0) 2018.12.30
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함