Exception not being caught in Coroutines

无人久伴 提交于 2020-02-22 03:19:54

问题


I can't seem to get my error-handling done in coroutines. I've been reading lots of articles and the exception handling documentation but I can't seem to get it working.

Here's my setup:

My ViewModel launches the coroutine with it's scope

class MyViewModel(private var myUseCase: MyUseCase) : ViewModel() {
    private val viewModelJob = Job()
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

    fun doSomething() {
        uiScope.launch {
            try {
                myUseCase()
            } catch (exception: Exception) {
                // Do error handling here
            }
        }
    }
}

My UseCase just handles a few logic and in this case a validator of some sort

class MyUseCase(private val myRepository: MyRepository) {
    suspend operator fun invoke() {
        if (checker()) {
            throw CustomException("Checker Failed due to: ...")
        }

        myRepository.doSomething()
    }
}

Then my Repository just handles the network layer / local layer

object MyRepository {
    private val api = ... // Retrofit

    suspend fun doSomething() = api.doSomething()
}

And here's my Retrofit interface

interface MyInterface {
    @POST
    suspend fun doSomething()
}

The try/catch from the ViewModel can handle the error from the Retrofit call however, it can't catch the error from the CustomException thrown by the UseCase. From articles I've been reading, this should work. If I use async I can do await and consume the error but I don't have to use async in this case and I've been wrapping my head around this. I might be getting lost.

Any help would be greatly appreciated! Thanks in advance!

Edit:

Here's the error log I'm getting:

com.example.myapp.domain.errors.CustomException
        at com.example.myapp.domain.FeatureOne$invoke$2.invokeSuspend(FeatureOne.kt:34)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)

The error directly points to the explicit throw statement.


回答1:


Trying with CoroutineExceptionHandler can be workaround for handling exceptions inside coroutines.

CoroutineExceptionHandler context element is used as generic catch block of coroutine where custom logging or exception handling may take place. It is similar to using Thread.uncaughtExceptionHandler.

How to use it?

val handler = CoroutineExceptionHandler { _, exception -> 
    println("Caught $exception") 
}
val job = GlobalScope.launch(handler) {
    throw AssertionError()
}
val deferred = GlobalScope.async(handler) {
    throw ArithmeticException() // Nothing will be printed, relying on user to call 
    deferred.await()
}
joinAll(job, deferred)

In your ViewModel, make sure that your uiScope is using SupervisorJob rather than Job. SupervisorJob's can handle its children's failure individually. Job would get cancelled unlike SupervisorJob

If you're using 2.1.0 for AAC Lifecycle and ViewModel, use the viewModelScope extension instead.




回答2:


As far as I know Retrofit hasn't still created the way to mark the methods with the suspend keyword. You can refer it to this link. So the correct way of your MyInterface would be:

interface MyInterface {
    @POST
    fun doSomething(): Deferred<Response<YourDataType>>
}


来源:https://stackoverflow.com/questions/56250403/exception-not-being-caught-in-coroutines

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!