I'm experimenting with handling exceptions in Kotlin coroutines on Android.
My use case is I want to do bunch of tasks on background(in async manner) and update multiple UI components on a single activity.
I've designed a BaseActivity structure to implement CoroutineScope so I can couple couroutines invoked with the lifecycle of activity.
Also, I've a Repository class which handles network calls.
I've achieved running multiple tasks concurrently. I know if I use a single Job object to cancel all coroutines on onDestroy() of the activity and doing (launch) multiple coroutines in activity, Exception in any single coroutine will cancel the Job from its CoroutineContext. And since Job is attached to lifecycle of activity, it will cancel all other coroutines too.
I've tried using a CoroutineExceptionHandler. It catches exception but cancels the Job too. Which in result cancels all other coroutines.
What I want?
Job object to attach with activity lifecycleAdding code below
class BaseActivity : AppCompatActivity(), CoroutineScope {
val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch(coroutineContext) {
Log.i("GURU", "launch1 -> start")
val result1Deferred = async { Repository().getData(1) }
val result2Deferred = async { Repository().getData(2) }
Log.i("GURU", "awaited result1 = " + result1Deferred.await() + result2Deferred.await())
}
//If Exception is Thrown, Launch1 should still continue to complete
advancedLaunch(coroutineContext) {
Log.i("GURU", "launch2 -> start")
val result1Deferred = async { Repository().getData(3) }
val result2Deferred = async { Repository().getData(4) }
delay(200)
throw Exception("Exception from launch 2")
Log.i("GURU", "awaited result2 = " + result1Deferred.await() + result2Deferred.await())
}
}
fun CoroutineScope.advancedLaunch(context: CoroutineContext = EmptyCoroutineContext,
exceptionBlock: (Throwable) -> Unit = {Log.i("GURU", it.message)},
launchBlock: suspend CoroutineScope.() -> Unit) {
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> exceptionBlock(throwable)}
launch(context + exceptionHandler) { launchBlock() }
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
Log.i("GURU", "job -> cancelled")
}
}
The Log Result of this is
I/GURU: launch1 -> start
I/GURU: launch2 -> start
I/GURU: getData -> start 1
I/GURU: getData -> start 2
I/GURU: getData -> start 4
I/GURU: getData -> start 3
I/GURU: Exception from launch 2
--------- beginning of crash
You might want to replace your Job with SupervisorJob.
It prevents exceptions from propagating "upwards" (one failed child will not cause entire job to fail), but still allows you to push cancellation "downwards" (to running children).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With