Oleksii Malovanyi
03/30/2021, 2:47 PMContainer.Settings
of type CoroutineExceptionHandler
, so we could have a default exception handler for Android ViewModel?Mikolaj Leszczynski
03/30/2021, 4:08 PMorbitDispatcher: CoroutineDispatcher
to orbitContext: CoroutineContext
, this would let you do what you want cause you can just go orbitContext = Dispatchers.Default + CoroutineExceptionHandler {…}
. WDYT?Mikolaj Leszczynski
03/30/2021, 4:09 PMOleksii Malovanyi
03/30/2021, 6:24 PMlaunch
override fun orbit(orbitFlow: suspend ContainerContext<STATE, SIDE_EFFECT>.() -> Unit) {
if (initialised.compareAndSet(expect = false, update = true)) {
scope.produce<Unit>(Dispatchers.Unconfined) {
awaitClose {
settings.idlingRegistry.close()
}
}
scope.launch(settings.handler) {
for (msg in dispatchChannel) {
launch(Dispatchers.Unconfined) { pluginContext.msg() }
}
}
}
dispatchChannel.offer(orbitFlow)
}
Oleksii Malovanyi
03/30/2021, 6:25 PMMikolaj Leszczynski
03/31/2021, 5:08 AMMikolaj Leszczynski
03/31/2021, 5:10 AMMikolaj Leszczynski
03/31/2021, 7:37 AMis invoked only on uncaught exceptions — exceptions that were not handled in any other way. In particular, all children coroutines (coroutines created in the context of another Job) delegate handling of their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root, so theCoroutineExceptionHandler
installed in their context is never used.CoroutineExceptionHandler
Mikolaj Leszczynski
03/31/2021, 7:38 AMOleksii Malovanyi
03/31/2021, 8:12 AMCoroutineExceptionHandler context element on a root coroutine can be used as genericso by the docs I guess it should work when installed into the root coroutine of the scope. The only thing that warns me is the channel range forblock for this root coroutine and all its children where custom exception handling may take place.catch
msg
- if child coroutine throws, the whole for-each parent coroutine is stopped, and then it seems like the msg(s) receiving process is stopped till the next call of the orbit
func. Maybe, it should be implemented with the help of additional supervisorScope
scope.launch {
for (msg in dispatchChannel) {
supervisorScope{
launch(settings.handler + Dispatchers.Unconfined) { pluginContext.msg() }
}
}
}
Oleksii Malovanyi
04/05/2021, 12:34 PMsupervisorScope
trick works well. Should I create a PR? Got changes ready anywayMikolaj Leszczynski
04/05/2021, 12:59 PMCoroutineName
, so specific error handling can be difficult
• With the default approach everything unhandled ends up in crashlytics. With the handler you have to set up some sort of non-fatal error logging in place in production, otherwise this could hide genuine issues.
In light of this I think we can include this as an option, with the default being the current behaviour.
Happy for you to open a PR, please take the above into account.Oleksii Malovanyi
04/05/2021, 1:03 PMinternal val defaultExceptionHandler by lazy { CoroutineExceptionHandler { _, t -> throw t } }
and
public class Settings(
public val sideEffectBufferSize: Int = Channel.UNLIMITED,
public val idlingRegistry: IdlingResource = NoopIdlingResource(),
public val orbitDispatcher: CoroutineDispatcher = Dispatchers.Default,
public val orbitExceptionHandler: CoroutineExceptionHandler = defaultExceptionHandler,
public val backgroundDispatcher: CoroutineDispatcher = defaultBackgroundDispatcher,
)
I’ll open PR soon, so we could proceed the discussion around the real code 😉Mikolaj Leszczynski
04/05/2021, 1:05 PMMikolaj Leszczynski
04/05/2021, 1:06 PMMikolaj Leszczynski
04/05/2021, 1:06 PMMikolaj Leszczynski
04/05/2021, 1:06 PMOleksii Malovanyi
04/06/2021, 6:45 AMMikolaj Leszczynski
04/06/2021, 6:46 AMMikolaj Leszczynski
04/07/2021, 6:49 AMOleksii Malovanyi
04/07/2021, 6:53 AMMikolaj Leszczynski
04/08/2021, 6:30 AMMikolaj Leszczynski
04/08/2021, 6:30 AMOleksii Malovanyi
04/08/2021, 6:33 AMMikolaj Leszczynski
04/08/2021, 6:34 AMOleksii Malovanyi
04/08/2021, 6:34 AM