Closed Skeptick closed 4 years ago
Hello @Skeptick, would you mind to provide a reproducer?
I'll try to do it on the weekend if I have time.
Thanks, this would really helpful!
Due to the fact that the application and views are very simple, the problem is reproduced much less often, but sooner or later it is reproduced :)
Thanks, I will check!
Also, for reproduce, you can run a similar coroutine when creating a Store
:
override suspend fun executeAction(action: Action, getState: () -> State) {
coroutineScope {
launch {
while (isActive) {
delay(1)
dispatch(Result.SomeResult)
}
}
}
}
That is, my case is not particularly rare. This can be achieved simply by having a Job updating the view state in the background. I checked, yield()
or if (coroutineContext.isActive) {...
or if (job?.isActive == true) {...
in binder also solved this problem.
@Skeptick Thanks! Could you please elaborate what you mean by having a Job updating the view state in the background
. View updates should be performed always on the main thread.
Oh yes, this is probably a bad description. I didn't mean a background thread, but something like "background work".
@Skeptick Thanks for the reproducer, this was really helpful.
So, the binder uses the Main
dispatcher by default, which posts events asynchronously. Actual view update happens when the Lifecycle
is already destroyed and the corresponding Job
is cancelled. This really looks like a bug in coroutines machinery. The coroutine is resumed from the suspention point and is being executed even if it is already cancelled. I would consider to file an issue for this case with a simplier reproducer.
I will add one of the checks suggested by you, thanks!
As a workaround you can use Dispatchers.Main.immediate
in the following way:
bind(viewLifecycle, BinderLifecycleMode.CREATE_DESTROY, Dispatchers.Main.immediate) { view.events bindTo store }
bind(viewLifecycle, BinderLifecycleMode.START_STOP, Dispatchers.Main.immediate) { store.states bindTo view }
In this case view updates will be performed in the same callstack.
I will also consider using Dispatchers.Main.immediate
by default, if possible.
Oh, thank you!
Hello! In my application, I store a navigation action in a state, and also use the
NavController::addOnDestinationChangedListener
to tell theStore
that the navigation action has been completed and can be nullified. Thus,Store
generates a new state at the very edge before destroying the view. Today I ran into the fact that 1 time out of 10, the state arrives in therender
method too late, and the application crashes due to the fact that the view has already nullified. ChangingBinderLifecycleMode
fromSTART_STOP
toRESUME_PAUSE
had no effect. I assumed that a coroutine does not have time to cancelJob
and addedyield()
here, beforeassertOnMainThread()
, and it seems to help. I was unable to reproduce the problem again. It will be great if you do it in the library :)