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