Closed Zhuinden closed 4 years ago
Repro:
@Test
public void navigationIsPossibleAndEnqueuedDuringActivationDispatch() {
final TestKey destination = new TestKey("destination");
Backstack backstack = new Backstack();
backstack.setScopedServices(new ServiceProvider());
class MyService
implements ScopedServices.Activated {
private final Backstack backstack;
public MyService(Backstack backstack) {
this.backstack = backstack;
}
@Override
public void onServiceActive() {
backstack.setHistory(History.of(destination), StateChange.REPLACE);
}
@Override
public void onServiceInactive() {
}
}
final MyService service = new MyService(backstack);
TestKeyWithOnlyParentServices beep = new TestKeyWithOnlyParentServices("beep", History.of("registration")) {
@Override
public void bindServices(ServiceBinder serviceBinder) {
if(serviceBinder.getScopeTag().equals("registration")) {
serviceBinder.addService("SERVICE", service);
}
}
};
TestKeyWithScope boop = new TestKeyWithScope("boop") {
@Override
public void bindServices(ServiceBinder serviceBinder) {
}
};
backstack.setup(History.of(boop));
backstack.setStateChanger(stateChanger);
backstack.setHistory(History.of(beep), StateChange.REPLACE);
assertThat(backstack.getHistory()).containsExactly(destination);
}
So to trigger the issue, you need:
a scope on the first key
a navigation call that would destroy the first scope, AND also activates a service that begins a navigation action right in place
The second navigation action ends, the scopes get cleaned up, then the first navigation action continues mid-activation-dispatch (as the second one started right there in place!) and by then, the second navigation action has cleared the scopes.
What a tricky bug. :thinking:
During activation dispatch, calling
backstack.setHistory(SomeTotallyOtherKey.create())
immediately destroys the scope hierarchy BEFORE the dispatch is complete.Gives you this beautiful crash that I was totally hoping to never see, surprised to have actually triggered it myself.
A possible solution would be to detach and re-attach the StateChanger while the dispatching is active.
But this is quite a pain in the ass and I'll write unit tests for this when I can. It's kinda past 4 AM. :thinking:
The quick-fix solution (in the code where
onActive()
callsbackstack.setHistory
) will be aHandler.post {}
.This is not as scary as it looks though, I've built stuff on
onScopeActive
before and apparently the "race" is only between actually triggering navigation that destroys scopes right there and then. That navigation action should be enqueued, but currently it is not.