Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle backstack between fragments and viewpagers?

I am using a ViewPager having 2 pages. First page of that ViewPager is MainFragment. MainFragment has ViewPager as bottomNavigationView. Each page has a FragmentContainerView and By default they contains HomeFragment, CommunityFragment and NotificationFragment respectively.

This is the Source Code and this is the APK of the project. So you can test it and improve it easily.

Now if i am in HomeFragment and I click on a profile button so It transact to ProfileFragment and from there setting and so on. And on clicking on back button it get back perfectly one-by-one. But it does not happens same with other FragmentContainerView. Even they get back directly to the parent fragment. Overall i am unable to handle the backstack between different ViewPagers and fragments.

To avoid the confusion of FragmentContainers i transact it like this

val containerId = (view?.parent as ViewGroup).id
activity?.supportFragmentManager?.beginTransaction()?.add(containerId, profileFragment)?.addToBackStack(null)?.commit()

Now the handling of BackPressed() in MainActivity is here

if (view_pager_main_activity?.currentItem == 0)
{
    if (view_pager_main_fragment?.currentItem == 0)
    {
        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view_home)
        val appbarHome = findViewById<AppBarLayout>(R.id.appbar_home)
        val layoutManager = recyclerView?.layoutManager as LinearLayoutManager
        when {
            layoutManager.findFirstCompletelyVisibleItemPosition() == 0 -> {
                super.onBackPressed()
            }
            supportFragmentManager.backStackEntryCount != 0 -> {
                supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
            }
            else -> {
                layoutManager.scrollToPositionWithOffset(0, 0)
                appbarHome.setExpanded(true)
                //recyclerView.smoothScrollToPosition(0)
            }
        }
    }
    else
    {
        // Otherwise, select the previous step.
        view_pager_main_fragment?.setCurrentItem(view_pager_main_fragment.currentItem - 1, false)
    }
}
else
{
    // Otherwise, select the previous step.
    view_pager_main_activity?.currentItem = view_pager_main_activity.currentItem - 1
}
like image 839
Stackoverflower Avatar asked Nov 06 '22 02:11

Stackoverflower


1 Answers

I checked your code and the problem was the usage of

supportFragmentManager.popBackStack(null,FragmentManager.POP_BACK_STACK_INCLUSIVE)

If you read the description of the flag POP_BACK_STACK_INCLUSIVE it says:

If set, and the name or ID of a back stack entry has been supplied, then all matching entries will be consumed until one that doesn't match is found or the bottom of the stack is reached. Otherwise, all entries up to but not including that entry will be removed.

So it was removing the multiple entries at once.

The reason it worked for Home screen was that it never reached that case. It always went in the first case of:

layoutManager.findFirstCompletelyVisibleItemPosition() == 0 -> {
                        super.onBackPressed()
                    }  

And that's what you exactly need for your case just call super.onBackPressed() when you want to pop only one fragment at once. So your final code becomes like this:

In your MainActivity's onBackPressed()

override fun onBackPressed() {
        if (view_pager_main_activity?.currentItem == 0) {
            if (view_pager_main_fragment?.currentItem == 0) {
                val recyclerView = findViewById<RecyclerView>(R.id.listRecyclerView)
                val appbarHome = findViewById<AppBarLayout>(R.id.appbar_home)
                val layoutManager = recyclerView?.layoutManager as LinearLayoutManager
                when {
                    layoutManager.findFirstCompletelyVisibleItemPosition() == 0 -> {
                        super.onBackPressed()
                    }
                    //This is never reached. It can be removed
                    supportFragmentManager.backStackEntryCount != 0 -> {
                        supportFragmentManager.popBackStack(
                            null,
                            FragmentManager.POP_BACK_STACK_INCLUSIVE
                        )
                    }
                    else -> {
                        layoutManager.scrollToPositionWithOffset(0, 0)
                        appbarHome.setExpanded(true)
                        //recyclerView.smoothScrollToPosition(0)
                    }
                }
            } else if (supportFragmentManager.backStackEntryCount != 0) {
                super.onBackPressed()
            } else {
                // Otherwise, select the previous step.
                view_pager_main_fragment?.setCurrentItem(
                    view_pager_main_fragment.currentItem - 1,
                    false
                )
            }
        } else if (supportFragmentManager.backStackEntryCount != 0) {
            super.onBackPressed()
        } else {
            // Otherwise, select the previous step.
            view_pager_main_activity?.currentItem = view_pager_main_activity.currentItem - 1
        }
    } 

Edit 1: From the comments

I want to clear the backstack when bottom nav selection changes.

If you want to clear the selection when you change the bottom navigation selection changes then you've to set a listener and clear the back stack in your MainFragment as following:

view?.bottomNavView?.setOnNavigationItemSelectedListener { item ->
            //check if item is being re-selected then just return and don't do anything
            if (bottomNavView.selectedItemId == item.itemId){
                return@setOnNavigationItemSelectedListener false
            }
            //if the bottom nav item selection changes then clear the back stack of previous tab
            parentFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)

            when (item.itemId) {
                R.id.homeFragment -> viewPager2.setCurrentItem(0, false)
                R.id.searchFragment -> viewPager2.setCurrentItem(1, false)
                R.id.notificationsFragment -> viewPager2.setCurrentItem(2, false)
            }
            true
        }  

And the rest can remain same. Try this out and do let me know.

like image 148
Mayur Gajra Avatar answered Nov 14 '22 23:11

Mayur Gajra