Search code examples
androidandroid-architecture-navigationandroid-viewpager2

How to navigate back from fragments inside NavHostFragment of ViewPager2?


What is the proper way of navigating back from nested fragments of ViewPager2?

Despite using app:defaultNavHost="true"with FragmentContainerView pressing back button while in a nested fragment of a page calls Activity's back press instead of navigating back to previous fragment.


Solution

  • As per the Create a NavHostFragment documentation, app:defaultNavHost="true" calls setPrimaryNavigationFragment() when the Fragment is first added - it is setPrimaryNavigationFragment() that routes back button press events to that fragment automatically.

    In a ViewPager2 though, it is the ViewPager2 that is responsible for creating and adding the Fragment. Since every level of the Fragment hierarchy needs to be the primary navigation fragment, adding a child fragment via XML still doesn't solve the missing link: that the Fragment in the ViewPager2 needs to be the primary navigation fragment.

    Therefore, you need to hook into the callbacks for when a Fragment is made the active Fragment and call setPrimaryNavigationFragment(). ViewPager2 1.1.0-alpha01 adds exactly this API in the FragmentTransactionCallback, specifically, the onFragmentMaxLifecyclePreUpdated(), which is called whenever the Lifecycle state of a Fragment is changed: when it is changed to RESUMED, that Fragment is now the active fragment and should become the primary navigation Fragment as part of the onPost callback.

    private class Adapter(parentFragment: Fragment) : FragmentStateAdapter(parentFragment) {
        init {
            // Add a FragmentTransactionCallback to handle changing
            // the primary navigation fragment
            registerFragmentTransactionCallback(object : FragmentTransactionCallback() {
                override fun onFragmentMaxLifecyclePreUpdated(
                        fragment: Fragment,
                        maxLifecycleState: Lifecycle.State
                ) = if (maxLifecycleState == Lifecycle.State.RESUMED) {
                    // This fragment is becoming the active Fragment - set it to
                    // the primary navigation fragment in the OnPostEventListener
                    OnPostEventListener {
                        fragment.parentFragmentManager.commitNow {
                            setPrimaryNavigationFragment(fragment)
                        }
                    }
                } else {
                    super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)
                }
            })
        }
    
        // The rest of your FragmentStateAdapter...
    }