'Issue with backstack and bottomnav in kotlin

I have a bottom nav with 4 fragments Home, Following, Notification, and Profile, there is no issue with the bottom navigation on backstack , but now for eg from profile fragment I jumped to a fragment called edit_profile which is not a part of the bottom nav and when press back I want that it should go back to the profile fragment but the backstack is taking me from edit_profile to directly home fragment

here is a recording link

I recently change my project from java to kotlin and I'm a beginner in kotlin

i really like the navigation of Pinterest and Instagram

Note:- All this code is automatically changed to kotlin (with some changes done manually ) , this issue was also with java and not after migrating to kotlin , Also if you want more reference of the code please tell me i will update the question

Code

MainActivity.kt // Bottom Nav

class MainActivity : AppCompatActivity() {
    var bottomNavigationView: BottomNavigationView? = null
    var integerDeque: Deque<Int> = ArrayDeque(3)
    var flag = true

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
        setContentView(R.layout.activity_main)
        val window = this.window
        window.statusBarColor = this.resources.getColor(R.color.black)
        bottomNavigationView = findViewById(R.id.bottom_navigation_view)
        integerDeque.push(R.id.nav_home)
        loadFragments(Home_Fragment())
        bottomNavigationView!!.selectedItemId = R.id.nav_home
        bottomNavigationView!!.setOnNavigationItemSelectedListener(
            BottomNavigationView.OnNavigationItemSelectedListener { item: MenuItem ->
                val id = item.itemId
                if (integerDeque.contains(id)) {
                    if (id == R.id.nav_home) {
                        integerDeque.size
                        if (flag) {
                            integerDeque.addFirst(R.id.nav_home)
                            flag = false
                        }
                    }
                    integerDeque.remove(id)
                }
                integerDeque.push(id)
                loadFragments(getFragment(item.itemId))
                false
            }
        )
    }

    @SuppressLint("NonConstantResourceId")
    private fun getFragment(itemId: Int): Fragment {
        when (itemId) {
            R.id.nav_home -> {
                bottomNavigationView!!.menu.getItem(0).isChecked = true
                return Home_Fragment()
            }
            R.id.nav_following -> {
                bottomNavigationView!!.menu.getItem(1).isChecked = true
                return Following_Fragment()
            }
            R.id.nav_notification -> {
                bottomNavigationView!!.menu.getItem(2).isChecked = true
                return Notification_Fragment()
            }
            R.id.nav_profile -> {
                bottomNavigationView!!.menu.getItem(3).isChecked = true
                return Profile_Fragment()
            }
        }
        bottomNavigationView!!.menu.getItem(0).isChecked = true
        return Home_Fragment()
    }

    private fun loadFragments(fragment: Fragment?) {
        if (fragment != null) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.fragment_container, fragment, fragment.javaClass.simpleName)
                .commit()
        }
    }

    override fun onBackPressed() {
        integerDeque.pop()
        if (!integerDeque.isEmpty()) {
            loadFragments(getFragment(integerDeque.peek()))
        } else {
            finish()
        }
    }

Edit_Profile.kt // from this fragment i want to go back to the last fragment which should be the profile fragment

class Edit_Profile : Fragment() {
    private var profilePhoto: CircleImageView? = null
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_edit_profile, container, false)
        profilePhoto = view.findViewById(R.id.circleImageView)
        initImageLoader()
        setProfileImage()
        val imageView = view.findViewById<ImageView>(R.id.backArrow)
        imageView.setOnClickListener {
            val newCase: Fragment = Profile_Fragment()
            assert(fragmentManager != null)
            val transaction = requireFragmentManager().beginTransaction()
            transaction.replace(R.id.fragment_container, newCase)
            transaction.addToBackStack(Profile_Fragment.toString())
            transaction.commit()
        }
        return view
    }

Edit

added a part of the transaction from Profile Fragment to Edit Profile

ProfileFragment.kt

editProfileButton!!.setOnClickListener(View.OnClickListener { v: View? ->
            val edit_profile: Fragment = Edit_Profile()
            requireActivity().getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.fragment_container, edit_profile,"TAG")
                .addToBackStack("TAG")
                .commit()
        })


Solution 1:[1]

Now you are managing the back stack through the integerDeque array.

  • When you go to a new BottomNavigationView fragment; you added its id to the array if it doesn't already exist.
  • When you pop up the back stack; the fragment at the top is kicked off the array.

But since you pushed all those ids in the bottomNavigationView.setOnItemSelectedListener callback; then the integerDeque array only contains BottomNavigationView fragments ids.

And as the Edit_Profile fragment is not a part of BottomNavigationView fragments, then it won't be added/popped off the queue. Instead when you try to popup the back stack whenever the Edit_Profile fragment is shown; the normal behavior you manage in the onBackPressed() continues and the Profile_Fragment id will pop up from the queue making you return to the preceding fragment (Home_Fragment) in your mentioned example.

A little fix to this is to consider adding an id into the queue when you transact to Edit_Profile fragment so that this id is popped off the queue resulting in back to Profile_Fragment fragment.

You can do that with the fragment's id in order to make sure it's unique:

editProfileButton!!.setOnClickListener(View.OnClickListener { v: View? ->
        val edit_profile: Fragment = Edit_Profile()
        requireActivity().getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.fragment_container, edit_profile,"TAG")
            .addToBackStack("TAG")
            .commit()
            
        (requireActivity() as MainActivity).integerDeque.push(id) // <<<< pushing id to the queue
        
    })

This should fix your problem.


Side tips:

  • Use setOnItemSelectedListener instead of setOnNavigationItemSelectedListener on the BNV as the latter is deprecated.
  • Return true instead of false from setOnItemSelectedListener callback as this should consume the event and mark the BNV as selected.
  • In Edit_Profile transaction replace the fragment instead of adding it with add as already the container is consumed; and this would make you avoid overlapping fragments in the container.
  • In onBackPressed(); you'd replace loadFragments(..) with bottomNavigationView.selectedItemId = integerDeque.peek(); this could be lighter to reuse the same fragment instead of redoing the transaction.

Solution 2:[2]

Usually I follow this pattern enter image description here

Where I add HomeF in main container which includes all bottom nav tab, and all bottom nav tab will open in home container, and those fragment which are not part of bottom nav will open in main container. I generally add(not replace) all the fragments in main container and set add to back stack , so that if user goes from profile (home_container) to something in main container , while backstack we can pop the top fragment and user will be seeing profile.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zain
Solution 2 sandeep dhami