Fixes issues in Router.pushController, Router.popController & Router.setBackstack where pushChangeHandler().removesFromViewOnPush() was not respected #666
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Motivation:
I encountered these issues while migrating existing code to androidX Navigation, and thus creating a
ControllerNavigator
along with others to support coexistingFragmentDialogs/Controllers
with the hope of being able to just plug inFragments
(if the need ever arises) or hopefullyCompose
when its more mature and time permits.Issue:
During this pursuit, I started leveraging
Router.setBackstack
for all changes to theRouter.backstack
, and in my test app I had a flow that looked like this:AboutController -> DialogController (with pushChangeHandler().removesFromViewOnPush() = false) -> DetailsController
When pressing back while on
DetailsController
, theDialogController
would re-appear, but withoutAboutController
being visible underneath. During debugging I found thatAboutController
was not properly detached, even though its view was removed during the transition fromDialogController -> DetailsController.
Another issue that would manifest is if we take the following backstack:
AboutController -> DialogController (with pushChangeHandler().removesFromViewOnPush() = false) -> DetailsAController -> DetailsBController
When pressing back while on
DetailsBController
, the transitions would not be as expected, since it would first transitionfrom(DetailsBController) to(null)
, and thenfrom(null) to(DetailsAController)
, rather than the expectedfrom(DetailsBController) to(DetailsAController)
.While I suppose not popping a dialog when navigating away from it, is atypical behaviour, I see no reason why it shouldn't be supported. A concrete use case in the app I'm working on is showing a dialog to select Time and then the option to open another dialog to select Date.
The Fix:
I added three test cases to
RouterTests
which proved the issue prior to my fix, during which I discovered thatpushController
shared the same issue assetBackstack
, and thatpopController
would not properly attach all views. I also updated the demo app to have let theDialogController
navigate to anotherController
, to trigger some of this behaviour and verify transitions looked as expected. I realise that this code may not be something you'd want in the repo, but I thought I'd leave it in a separate commit, for your testing convenience - and for ease of removal if need be.Inspired by how
replaceTopController
worked, I used that, along with ensuring thatgetVisibleTransactions
would no longer incorrectly assume that any transaction prior to aremovesFromViewOnPush=false
transaction, is visible.Perhaps there are other cases which I didn't think to trigger, so hopefully whoever reviews this has more intimate knowledge of the inner
Router
workings, and can have a think as to whether there are other cases, or whether it would be better to changeperformControllerChange
to handle some of these fixes too.The issues seem related to #608, since both are about
removesFromViewOnPush
when manipulating the backstack, but I also noticed thatViewLeakTests.testViewRemovedIfLayeredNotRemovesFromViewOnPush
was making the assumption that theController
which comes before the one withremovesFromViewOnPush=false
remains attached even when pushing a newController
withremovesFromViewOnPush=true
on top - which to me was the whole issue.Thanks for many years of Conductor-awesomeness, I've truly been happy using it, just as I am happy to be able to contribute meaningfully (as you hopefully agree with! :) )
/Christian
Some brief video recordings of two issues before and after my fix:
router_removesFromViewOnPush_setBackstack_issue.mp4
router_removesFromViewOnPush_setBackstack_fixed.mp4
router_removesFromViewOnPush_pushController_issue.mp4
router_removesFromViewOnPush_pushController_fixed.mp4