androidandroid-fragmentsfragmentmanager

Fragment Transaction is added to backstack even if disallowAddToBackStack is called


Here is a sample code to repo this bug:

If I replace 3 fragments in a row, and disable the second one to be added into the backstack.

        fragmentManager = supportFragmentManager
        val fargmentA = FragmentA()
        fragmentManager.beginTransaction().replace(R.id.container, fargmentA).addToBackStack("a").commitAllowingStateLoss();

        val fargmentB = FragmentB()
        fragmentManager.beginTransaction().replace(R.id.container, fargmentB).disallowAddToBackStack().commitAllowingStateLoss();
        
        val fargmentC = FargmentC()
        fragmentManager.beginTransaction().replace(R.id.container, fargmentC).addToBackStack("c").commitAllowingStateLoss();

After the above transaction, I called popBackStack():

  val count = fragmentManager.backStackEntryCount;
        for(i in 0 until count ){
            fragmentManager.popBackStackImmediate()
        }

I can still see the onCreateView() method is called from FragmentB which I disallow add to backstack.

Is this a known bug or just how fragment manager behaves?

Thanks!


Solution

  • There's two main things to keep in mind:

    1. replace() is the equivalent to calling remove() on every fragment currently added to that container, then calling add() with your new fragment, so that transaction with fragmentC is going to remove fragmentB as part of its operation
    2. popBackStack() puts you exactly in the state you were in before the transaction. This means that fragmentC gets removed (the opposite of add()) and fragmentB gets added (the opposite of remove())

    This means that it is expected that popping your fragmentC transaction will bring fragmentB back - your disallowAddToBackStack() just means that your fragmentB transaction can never be reversed.

    This actually has some serious implications when it comes to mixing addToBackStack() and non-addToBackStack() fragment transactions on the same container: after you do your fragmentB transaction, there's no way to get back to fragmentA.

    Generally, this means that if that was actually want you wanted, you replace your non-back stack fragmentB transaction with

    fragmentManager.popBackStack()
    val fargmentB = FragmentB()
    fragmentManager.beginTransaction().replace(R.id.container, fargmentB).addToBackStack("b").commitAllowingStateLoss();
    

    This would ensure that all transactions use addToBackStack() and the reversal works as expected at each step.

    Note that every one of your transactions should use setReorderingAllowed(true) as per the Fragment transaction guide. This will prevent cases where fragments are moved up to higher lifecycle levels then moved immediately back down and ensures that combined operations like a popBackStack() and commit() together run as a single atomic transition.