Reputation: 2557
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.
Upvotes: 11
Views: 5607
Reputation: 237
Another way to resolve this would be to covert your custom error object to implement CancellationException
For eg:
Your CustomException
can be implemented as :
sealed class CustomError : CancellationException() {
data class CustomException(override val message: String = "Checker Failed due to: ...") : CustomError
}
This exception would get caught in the try/catch block of the view model
Upvotes: 1
Reputation: 12138
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.
Upvotes: 5
Reputation: 8371
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>>
}
Upvotes: 0