Using try catch block in swallowing exceptions when using kotlin coroutines

Solution 1:

I suggest to use sealed Result class and try/catch block to handle api response exceptions:

sealed class Result<out T : Any>
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable, val message: String = exception.localizedMessage) : Result<Nothing>()

inline fun <T : Any> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
    if (this is Success) action(data)
    return this
}
inline fun <T : Any> Result<T>.onError(action: (Error) -> Unit): Result<T> {
    if (this is Error) action(this)
    return this
}

Catch exceptions in PokemonListInteractorImp using try/catch block and return appropriate Result:

class PokemonListInteractorImp(private val pokemonService: PokemonService) : PokemonListInteractor {
    override suspend fun getListOfPokemons(): Result<PokemonListModel> {
        return withTimeout(10_000) {
            try {
                Success(pokemonService.getPokemons())
            } catch (e: Exception) {
                Error(e)
            }
        }
    }
}

In your ViewModel you can use extension functions onSuccess, onError on Result object to handle the result:

fun fetchPokemons() = viewModelScope.launch {
    shouldShowLoading.value = true
    pokemonListInteractor.getListOfPokemons()
            .onSuccess { pokemonListLiveData.value = it }
            .onError { errorMessage.value = it.message }
    shouldShowLoading.value = false
}

Solution 2:

As you use launch coroutine builder, it bubbles up the exceptions. So I think CoroutineExceptionHandler will be an alternative way of handling uncaught exceptions in a more idiomatic way. The advantages are

  • the exceptions thrown inside coroutines won't be swallowed and you have better visibility
  • you can cleanly test exception propagation and handling (if you implement the exception handler) in coroutines
  • you can reduce/avoid boilerplate try/catch

Take a look at this example; I have tried to showcase a few scenarios;

/**
 * I have injected coroutineScope and the coroutineExceptionHandler in the constructor to make this class
 * testable. You can easily mock/stub these in tests.
 */
class ExampleWithExceptionHandler(
    private val coroutineScope: CoroutineScope = CoroutineScope(
        Executors.newFixedThreadPool(2).asCoroutineDispatcher()
    ),
    private val coroutineExceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
        println(
            "Exception Handler caught $throwable, ${throwable.suppressed}" //you can get the suppressed exception, if there's any.
        )
    }
) {
    /**
     * launch a coroutine with an exception handler to capture any exception thrown inside the scope.
     */
    fun doWork(fail: Boolean): Job = coroutineScope.launch(coroutineExceptionHandler) {
        if (fail) throw RuntimeException("an error...!")
    }

}

object Runner {

    @JvmStatic
    fun main(args: Array<String>) {
        val exampleWithExceptionHandler = ExampleWithExceptionHandler()
        //a valid division, all good. coroutine completes successfully.
        runBlocking {
            println("I am before doWork(fail=false)")
            exampleWithExceptionHandler.doWork(false).join()
            println("I am after doWork(fail=false)")
        }
        //an invalid division. Boom, exception handler will catch it.
        runBlocking {
            println("I am before doWork(fail=true)")
            exampleWithExceptionHandler.doWork(true).join()
            println("I am after doWork(fail=true)")
        }

        println("I am on main")
    }
}

Output

I am before doWork(fail=false)
I am after doWork(fail=false)
I am before doWork(fail=true)
Exception Handler caught java.lang.RuntimeException: an error...!, [Ljava.lang.Throwable;@53cfcb7a
I am after doWork(fail=true)
I am on main

You can see the exception has been captured by the handler successfully. If coroutines are nested, you can get the inner exception with suppressed method.

This approach is good for non-async coroutines. The async coroutines are a different beast. If you try to await on an async coroutine inside the same runBlocking code, the exceptions won't be handled propagated like launch type. It will still throw out of the scope and kills the main thread. For async, I saw that you can use supervisorScope or wrapped coroutine (which I haven't got a chance to use).

As propagated unhandled exceptions can be handled globally. This style can help you with reuse of exception handler code and any subsequent operations. For example, the docs suggest;

Normally, the handler is used to log the exception, show some kind of error message, terminate, and/or restart the application.

A similar approach can be found when you use Spring framework with global exception handlers.

Possible drawbacks are;

  • This is only suitable for uncaught exceptions and is not recoverable
  • This may look like AOP style code
  • Returning different values based on exceptions could concentrate the logic in the exception handler.
  • Have to have good understand of how exceptions are propagated depending on the coroutine builders and scopes use

About suspension, If your API/functions are fully async, you can return the Job or Deferred<T> created by the coroutine scope. Otherwise, you have to block somewhere in your code to complete the coroutine and return the value.

This doc is very useful https://kotlinlang.org/docs/reference/coroutines/exception-handling.html

Another good resource specific to Android apps - https://alexsaveau.dev/blog/kotlin/android/2018/10/30/advanced-kotlin-coroutines-tips-and-tricks/#article

Solution 3:

In your PokemonListInteractorImp class, handle response exception and do with it whatever you wanna. In ViewModel, where you set value to some LiveData object your List, this should already be success state. Try something like:

protected suspend fun <T> requestApiCall(call: suspend () -> T): Either<FailureState, T> {
        return try {
            Either.Right(call.invoke())
        } catch (e: HttpException) {
            return Either.Left(FailureState.ServerError)
        } catch (e: UnknownHostException) {
            return Either.Left(FailureState.NetworkConnection)
        } catch (e: Throwable) {
            e.printStackTrace()
            return Either.Left(FailureState.UnknownError)
        }
    }

Failure state class:

sealed class FailureState {
    object NetworkConnection : FailureState()
    object ServerError : FailureState()
    object UnknownError : FailureState()

    /** * Extend this class for feature specific failures.*/
    abstract class FeatureFailure: FailureState()
}

ViewModel, something like:

    fun loadQuestions(type: String) {
            viewModelScope.launch {
                questionsUseCase.invoke(type).fold(::handleError, ::handleUsersResponse)
            }
        }

 private fun handleUsersResponse(questionsResponse: QuestionsResponse) {
        questionsResponse.questions?.apply {
            postScreenState(ShowQuestions(map { it.toDomainModel() }.toMutableList()))
        }
    }

Something like that, hope it helps. But, if you are looking just to handle exceptions in Coroutines, here is good source: https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c#:~:text=Coroutines%20use%20the%20regular%20Kotlin,treat%20exceptions%20in%20different%20ways.

If you have any question, just ask.