Search code examples
androidandroid-fragmentskotlinandroid-fragmentactivityfragmentmanager

Why Fragmenttransaction replace() acts like a add()?


I have here a sample GitHub project. It's just an activity inflating other fragments in it, by tapping bottom tabs:

  1. Applaunch inflates Fragment 1
  2. Pressing 1.Tab -> inflates Fragment 1
  3. Pressing 2.Tab -> inflates Fragment 2
  4. Pressing 3.Tab -> inflates Fragment 3

and all used by means of this.supportFragmentManager?.beginTransaction()?.replace(). For me it acts like a FragmentTransaction.add(), because:

When I start the app (Fragment 1 is loaded) and press

  • 2nd Tab for Fragment 2 (Toast Frag 2)
  • and 3rd Tab for Fragment 3 (Toast Frag 3)
  • then backbutton (Toast Frag 2)
  • again backbutton (Toast Frag 1)

All the stack is working back. So nothing was "replaced", everything was added?

Whenever I press back, I would like to load "Fragment 1", which is the initial Fragment for the activity. How?


Solution

  • I guess it is happening because the fragments are being replaced but they are also being added to the BackStack as follows:

    BaseActivity.kt

    // Note the .addToBackStack(null)
    this.supportFragmentManager?.beginTransaction()?.replace(resId, newFragment)?.addToBackStack(null)?.commit()
    

    From Documentation for addToBackStack

    Add this transaction to the back stack. This means that the transaction will be remembered after it is committed, and will reverse its operation when later popped off the stack.

    You may want to check this question to check more info about the difference between add, replace and addToBackStack.

    If you don't wanna to "remember and restore" the fragment transition, just remove the call to addToBackStack.

    Edit

    If you always want to return to first fragment, you can do:

    Keep the addToBackStack(). So, the navigation history will be retained

    this.supportFragmentManager?.beginTransaction()?.replace(resId, 
    

    newFragment)?.addToBackStack(null)?.commit()

    Override onBackPressed on MainActivity or BaseActivity and request to always return to first transaction commit:

    override fun onBackPressed() {
        if(this.supportFragmentManager?.getBackStackEntryCount() > 0) {
            this.supportFragmentManager?.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
        } else {
           super.onBackPressed()
        }
    }
    

    Just note this is a test code and I could not verify. Also, I'm not familiar with Kotlin. So, this was the best code I could come with... Test and let me know the result... For any case, you can get the ideia.

    Another option:

    Remove the addToBackStack():

    this.supportFragmentManager?.beginTransaction()?.replace(resId, newFragment)?.addToBackStack(null)?.commit()
    

    Then, you must manually control which fragment should be displayed when user presses the back key... something like:

    private var int : mCurrentFragment = 1
    
    private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when (item.itemId) {
            R.id.navigation_home -> {
                mCurrentFragment = 1
                ...
            }
            R.id.navigation_dashboard -> {
                mCurrentFragment = 2
                ...
            }
            R.id.navigation_notifications -> {
                mCurrentFragment = 3
                ...
            }
        }
        false
    }
    
    override fun onBackPressed() {
        if(mCurrentFragment != 1) {
            replaceFragment(R.id.container_main_layout, Fragment1())
        } else {
           super.onBackPressed()
        }
    }