I have an application with a BottomNavigationView and ViewPager.
How is it possible to implement it using new "Navigation Architecture Component?"
What is the best practice?
Thanks so much
I have an application with a BottomNavigationView and ViewPager.
How is it possible to implement it using new "Navigation Architecture Component?"
What is the best practice?
Thanks so much
 
    
     
    
    UPDATE (15/06/21):
Starting from Navigation component version 2.4.0-alpha01 multiple back stacks are supported out of the box. According to documentation if you are using NavigationView or BottomNavigationView together with Navigation component, then multiple back stacks should work without any code changes to previous implementation.
As part of this change, the NavigationUI methods of onNavDestinationSelected(), BottomNavigationView.setupWithNavController() and NavigationView.setupWithNavController() now automatically save and restore the state of popped destinations, enabling support for multiple back stacks without any code changes. When using Navigation with Fragments, this is the recommended way to integrate with multiple back stacks.
Original Answer:
Default implementation of BottomNavigationView with Navigation Arch Component didn't work out for me. When clicking on tabs it starts them from beginning according to navigation graph.
I need to have 5 tabs in the bottom of the screen and have a separate back stack for each of the tabs. Which means when switching between tabs you will always return to the exactly the same state as it was before leaving (like in Instagram).
My approach is as follows:
ViewPager and BottomNavigationView in activity_main.xmlOnNavigationItemSelectedListener to BottomNavigationView in MainActivity.ktNavHostFragment inside of Container fragments' xml.Note: each of the graphs can interact with each other.
Important point here is that we place Toolbar not in activity but in Container fragment. Then we call setupWithNavController() on toolbar itself without setting it as supportActionBar. This way toolbar titles will be automatically updated and Back/Up button will be managed automatically.
Results:
SafeArgs and DeepLinking works as expected.BottomNavigationManager and ViewPager (i.e. we can implement OnNavigationItemReselectedListener and decide to scroll lists in current tab to top before popping back stack).Code:
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <androidx.viewpager.widget.ViewPager
        android:id="@+id/main_view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/main_bottom_navigation_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?android:attr/windowBackground"
        app:menu="@menu/navigation" />
</LinearLayout>
MainActivity.kt
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
    private lateinit var viewPagerAdapter: ViewPagerAdapter
    private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when (item.itemId) {
            R.id.navigation_tab_1 -> {
                main_view_pager.currentItem = 0
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_tab_2 -> {
                main_view_pager.currentItem = 1
                return@OnNavigationItemSelectedListener true
            }
        }
        false
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
        main_view_pager.adapter = viewPagerAdapter
        
        main_bottom_navigation_view.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
    }
}
ViewPagerAdapter.kt
class ViewPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
    override fun getItem(position: Int): Fragment {
        return when (position) {
            0 -> Tab1ContainerFragment()
            else -> Tab2ContainerFragment()
        }
    }
    override fun getCount(): Int {
        return 2
    }
}
fragment_tab_1_container.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Tab1ContainerFragment">
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/tab_1_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark" />
    <fragment
        android:id="@+id/tab_1_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/navigation_graph_tab_1" />
</RelativeLayout>
Tab1ContainerFragment.kt
class Tab1ContainerFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_tab_1_container, container, false)
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val toolbar = view.findViewById<Toolbar>(R.id.tab_1_toolbar)
        val navHostFragment = childFragmentManager.findFragmentById(R.id.tab_1_nav_host_fragment) as NavHostFragment? ?: return
        val navController = navHostFragment.navController
        val appBarConfig = AppBarConfiguration(navController.graph)
        toolbar.setupWithNavController(navController, appBarConfig)
    }
}
We can create as many navigation graphs as you want:
But we need to have a separate graph for each tabs:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/navigation_graph_tab_1"
    app:startDestination="@id/tab1StartFragment">
    <fragment
        android:id="@+id/tab1StartFragment"
        android:name="com.marat.android.bottomnavigationtutorial.Tab1StartFragment"
        android:label="fragment_tab_1_start"
        tools:layout="@layout/fragment_tab_1_start">
        <action
            android:id="@+id/action_tab_1_to_content"
            app:destination="@id/navigation_graph_content" />
    </fragment>
    <include app:graph="@navigation/navigation_graph_content" />
</navigation>
Here start destination fragment is any fragment you want to appear as first screen in tab.
 
    
    I created a sample that has Toolbar on Activity, you can also create ViewPager fragments that have their own toolbar. It uses OnBackPressedCallback for back navigation, ViewModel for setting current NavController and NavHostFragment with childFragmentManager or nested fragments, and respects life cycle with viewLifeCycleOwner and disabling callback on pause and enabling onResume.
Navigation and layout architecture
     MainActivity(Appbar + Toolbar  + ViewPager2 + BottomNavigationView)
       |
       |- HomeNavHostFragment
       |  |- HF1 -> HF2 -> HF3
       |
       |- DashboardNavHostFragment
       |  |- DF1 -> DF2 -> DF3
       |
       |- NotificationHostFragment
          |- NF1 -> NF2 -> NF3
First, create a navigation graph for each tab or fragment of ViewPager2
nav_graph_home.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph_dashboard"
    app:startDestination="@id/dashboardFragment1">
    <fragment
        android:id="@+id/dashboardFragment1"
        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment1"
        android:label="DashboardFragment1"
        tools:layout="@layout/fragment_dashboard1">
        <action
            android:id="@+id/action_dashboardFragment1_to_dashboardFragment2"
            app:destination="@id/dashboardFragment2" />
    </fragment>
    <fragment
        android:id="@+id/dashboardFragment2"
        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment2"
        android:label="DashboardFragment2"
        tools:layout="@layout/fragment_dashboard2">
        <action
            android:id="@+id/action_dashboardFragment2_to_dashboardFragment3"
            app:destination="@id/dashboardFragment3" />
    </fragment>
    <fragment
        android:id="@+id/dashboardFragment3"
        android:name="com.smarttoolfactory.tutorial7_1bnw_viewpager2_nestednavigation.blankfragment.DashboardFragment3"
        android:label="DashboardFragment3"
        tools:layout="@layout/fragment_dashboard3" >
        <action
            android:id="@+id/action_dashboardFragment3_to_dashboardFragment1"
            app:destination="@id/dashboardFragment1"
            app:popUpTo="@id/dashboardFragment1"
            app:popUpToInclusive="true" />
    </fragment>
</navigation>
other nav graphs are the same as this one
BottomNavigationView's menu
menu_bottom_nav.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:id="@+id/nav_graph_home"
            android:icon="@drawable/ic_baseline_home_24"
            android:title="Home"/>
    <item
            android:id="@+id/nav_graph_dashboard"
            android:icon="@drawable/ic_baseline_dashboard_24"
            android:title="Dashboard"/>
    <item
            android:id="@+id/nav_graph_notification"
            android:icon="@drawable/ic_baseline_notifications_24"
            android:title="Notification"/>
</menu>
ViewPager2 adapter
class ActivityFragmentStateAdapter(fragmentActivity: FragmentActivity) :
    FragmentStateAdapter(fragmentActivity) {
    
    override fun getItemCount(): Int = 3
    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> HomeNavHostFragment()
            1 -> DashBoardNavHostFragment()
            else -> NotificationHostFragment()
        }
    }
}
Layout for main activity
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar" />
        </com.google.android.material.appbar.AppBarLayout>
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
            <androidx.viewpager2.widget.ViewPager2
                android:id="@+id/viewPager"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintBottom_toTopOf="@id/bottom_nav"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
            <com.google.android.material.bottomnavigation.BottomNavigationView
                android:id="@+id/bottom_nav"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:menu="@menu/menu_bottom_nav" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
MainActivity both listens for BottomNavigationView's item change and change of current NavController when we change tabs, because we have to set Appbar navigation for each tab.
class MainActivity : AppCompatActivity() {
//    private val appbarViewModel by viewModels<AppbarViewModel>()<AppbarViewModel>()
    private val appbarViewModel:AppbarViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        val viewPager2 = dataBinding.viewPager
        val bottomNavigationView = dataBinding.bottomNav
        // Cancel ViewPager swipe
        viewPager2.isUserInputEnabled = false
        // Set viewpager adapter
        viewPager2.adapter = ActivityFragmentStateAdapter(this)
        
        // Listen bottom navigation tabs change
        bottomNavigationView.setOnNavigationItemSelectedListener {
            when (it.itemId) {
                R.id.nav_graph_home -> {
                    viewPager2.setCurrentItem(0, false)
                    return@setOnNavigationItemSelectedListener true
                }
                R.id.nav_graph_dashboard -> {
                    viewPager2.setCurrentItem(1, false)
                    return@setOnNavigationItemSelectedListener true
                }
                R.id.nav_graph_notification -> {
                    viewPager2.setCurrentItem(2, false)
                    return@setOnNavigationItemSelectedListener true
                }
            }
            false
        }
        appbarViewModel.currentNavController.observe(this, Observer { navController ->
            navController?.let {
                val appBarConfig = AppBarConfiguration(it.graph)
                dataBinding.toolbar.setupWithNavController(it, appBarConfig)
            }
        })
    }
}
AppbarViewModel has only one MutableLiveData to set current NavController. Purpose of using ViewModel to set ViewModel in NavHost fragments and and being able to get it in Activity or other fragment.
class AppbarViewModel : ViewModel() {
    val currentNavController = MutableLiveData<NavController?>()
}
Layout for NavHost which has FragmentContainerView, when i put Toolbar into these fragments and use FragmentContainerView i get error, use fragment if you use appBar with navigation.
fragment_navhost_home.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nested_nav_host_fragment_home"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:defaultNavHost="false"
            app:navGraph="@navigation/nav_graph_home"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
NavHost Fragment that contains child fragments and NavController, 3 of them are identical so i only put one
class HomeNavHostFragment : BaseDataBindingFragment<FragmentNavhostHomeBinding>() {
    override fun getLayoutRes(): Int = R.layout.fragment_navhost_home
    private val appbarViewModel by activityViewModels<AppbarViewModel>()
    private var navController: NavController? = null
    private val nestedNavHostFragmentId = R.id.nested_nav_host_fragment_home
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val nestedNavHostFragment =
            childFragmentManager.findFragmentById(nestedNavHostFragmentId) as? NavHostFragment
        navController = nestedNavHostFragment?.navController
        // Listen on back press
        listenOnBackPressed()
    }
    private fun listenOnBackPressed() {
        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
    }
    override fun onResume() {
        super.onResume()
        callback.isEnabled = true
        // Set this navController as ViewModel's navController
        appbarViewModel.currentNavController.value = navController
    }
    override fun onPause() {
        super.onPause()
        callback.isEnabled = false
    }
    /**
     * This callback should be created with Disabled because on rotation ViewPager creates
     * NavHost fragments that are not on screen, destroys them afterwards but it might take
     * up to 5 seconds.
     *
     * ### Note: During that interval touching back button sometimes call incorrect [OnBackPressedCallback.handleOnBackPressed] instead of this one if callback is **ENABLED**
     */
    val callback = object : OnBackPressedCallback(false) {
        override fun handleOnBackPressed() {
            // Check if it's the root of nested fragments in this navhost
            if (navController?.currentDestination?.id == navController?.graph?.startDestination) {
                Toast.makeText(requireContext(), "AT START DESTINATION ", Toast.LENGTH_SHORT)
                    .show()
                /*
                    Disable this callback because calls OnBackPressedDispatcher
                     gets invoked  calls this callback  gets stuck in a loop
                 */
                isEnabled = false
                requireActivity().onBackPressed()
                isEnabled = true
            } else {
                navController?.navigateUp()
            }
        }
    }
}
Important things to be aware with nested navigation are
First of all, you need to check out if you are the start destination of the graph, because you need to call requireActivity().onBackPressed() to call Activity back or you get stuck at HomeFragment1 for instance
If you don't disable callback before calling requireActivity().onBackPressed() you get stuck in a loop because onBackPressed also calls Active callbacks
If you don't disable callback.isEnabled = false when your current Fragment is not visible every callback gets called
And finally and i think the most important one is if you rotate your device
Fragments in other tabs also get created by viewPager, then destroyed 3 to 5 later, but their onResume is not called, this causes other callbacks to call handleBackPressed if you create object : OnBackPressedCallback(true), use
object : OnBackPressedCallback(false)
For instance if callback is active and you rotate device when HomeFragment3 is open and you touch back button while callback is active
2020-06-28 13:23:42.722 I:  HomeNavHostFragment #208670033  onCreate()
2020-06-28 13:23:42.729 I: ⏰ NotificationHostFragment #19727909  onCreate()
2020-06-28 13:23:42.826 I:  HomeNavHostFragment #208670033  onViewCreated()
2020-06-28 13:23:42.947 I: ⏰ NotificationHostFragment #19727909  onViewCreated()
2020-06-28 13:23:42.987 I:  HomeNavHostFragment #208670033  onResume()
2020-06-28 13:23:44.092 I: ⏰ NotificationHostFragment #19727909 handleOnBackPressed()
2020-06-28 13:23:44.851 I: ⏰ NotificationHostFragment #19727909 handleOnBackPressed()
2020-06-28 13:23:53.011 I: ⏰ NotificationHostFragment #19727909  onDestroyView()
2020-06-28 13:23:53.023 I: ⏰ NotificationHostFragment #19727909  onDestroy()
Even i press back button twice while HomeFragment3 is visible, ⏰ NotificationHostFragment #19727909 handleOnBackPressed() is invoked because ViewPager creates the fragments that are also not visible and destroys them afterwards. It took 10 seconds in my instance, you can also try it out.
EDIT: Instead of onBackPressedDispatcher in each fragment of ViewPager 2, it's advised to use the snipped below in FragmentStateAdapter which sets active fragment on screen as primary navigation fragment.
/**
 * FragmentStateAdapter to add ability to set primary navigation fragment
 * which lets fragment visible to be navigable when back button is pressed using
 * [FragmentStateAdapter.FragmentTransactionCallback] in [ViewPager2].
 *
 * *  Create FragmentStateAdapter with viewLifeCycleOwner instead of Fragment to make sure
 * that it lives between [Fragment.onCreateView] and [Fragment.onDestroyView] while [View] is alive
 *
 * * https://stackoverflow.com/questions/61779776/leak-canary-detects-memory-leaks-for-tablayout-with-viewpager2
 */
abstract class NavigableFragmentStateAdapter(
    fragmentManager: FragmentManager,
    lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {
    private val fragmentTransactionCallback =
        object : FragmentStateAdapter.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)
            }
        }
    init {
        // Add a FragmentTransactionCallback to handle changing
        // the primary navigation fragment
        registerFragmentTransactionCallback()
    }
    fun registerFragmentTransactionCallback() {
        registerFragmentTransactionCallback(fragmentTransactionCallback)
    }
    fun unregisterFragmentTransactionCallback() {
        unregisterFragmentTransactionCallback(fragmentTransactionCallback)
    }
}
Here is the link for full sample. You can also put Toolbar to each navHost fragment, it's a little bit simpler.
Which you call in NavHost fragment with Toolbar
val appBarConfig = AppBarConfiguration(navController!!.graph)
dataBinding.toolbar.setupWithNavController(navController!!, appBarConfig)
 
    
    A solution for me was to leave the fragment in the ViewPager out of the navigation and directly set the actions on the pages fragment as if these pages were the host. To explain it better :
Say you are in Fragment A with a ViewPager of Fragment B And you try to navigate from B to C
In Fragment B, use ADirections class and an action from A to C. findNavHost().navigateTo(ADirections.ActionFromAtoC)
 
    
    I have implemented Android Arch Navigations with viewpager. please have a look. Any improvments are welcome. Lets learn togather.
https://github.com/Maqsood007/AndroidJetpack/tree/master/ArchNavViewPagerImpl
 
    
    I have written a related article on this regarding view pagers, specifically focusing on Master-Detail fragments which are tabbed, but the same logic applies to regular ViewPagers. The code is located here.
 
    
    Thanks to @Marat - he provided great solution. In my case I have list/detail-view navigation for the second ViewPager's view and use Fullscreeen mode without any Action\Tool-bar :
Want to comment some moments:
1) It's possible and easy for me to use one common graph for one page:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/page2Coordinator"
    app:startDestination="@id/Fragment_2Coordinator">
    <fragment
        android:id="@+id/Fragment_2Coordinator"
        android:name="my.app.Fragment_2Coordinator"
        android:label="Fragment_2Coordinator">
        <action
            android:id="@+id/action_showList_2A"
            app:destination="@id/Fragment_2A" />
    </fragment>
    <fragment
        android:id="@+id/Fragment_2A"
        android:name="my.app.Fragment_2A"
        android:label="Fragment_2A">
        <action
            android:id="@+id/action_goToDetail_2B"
            app:destination="@id/Fragment_2B" />
    </fragment>
    <fragment
        android:id="@+id/Fragment_2B"
        android:name="my.app.Fragment_2B"
        android:label="Fragment_2B">
        <action
            android:id="@+id/action_backToList_2A"
            app:destination="@id/Fragment_2A" />
    </fragment>
</navigation>
2) Instead of operations with toolbar in Fragment_2Coordinator.onViewCreated() simply navigate with action in graph (in case you don't use system navigation):
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val navHostFragment = childFragmentManager.findFragmentById(R.id. tab_1_nav_host_fragment) as NavHostFragment? ?: return
    val navController = navHostFragment.navController
    navController.navigate(R.id.action_showList_2A)
}
3) To provide a return from 2B to 2A with phone Back button - go to Activity:
class MainActivity : AppCompatActivity() {
 .  .  .  .  . 
    override fun onBackPressed() {
        val navController = findNavController(R.id.tab_1_nav_host_fragment)
        when(navController.currentDestination?.id) {
            R.id.Fragment_2B -> {
                navController.navigate(R.id.action_backToList_2A)
            }
            else -> {
                super.onBackPressed()
            }
        }
        println()
    }
}
 
    
    I have a MainFragment which is hosting Fragment A, Fragment B and Fragment C inside a viewPager.
And I want to open Fragment D from Fragment B (hosted by viewPager inside MainFragment).
So I created an action from MainFragment to Fragment D and called from Fragment B
val direction = FragmentMainDirections.actionFragmentMainToFragmentD()
findNavController().navigate(direction)
Works.
 
    
    In addition to Marat's answer in order to have back stack working with back button in each fragment, you have to add this to your container fragment onViewCreated:
val callback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (!navHostFragment.navController.popBackStack()) {
                    isEnabled = false
                    activity?.onBackPressed()
                }
            }
        }
activity?.onBackPressedDispatcher?.addCallback(this, callback)
 
    
    We can implement using bottom navigation component and NavigationGraph easily.
You should create corresponding fragment for every bottom navigation menu
nav_graph.xml
 <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/nav_graph"
        app:startDestination="@id/actionHome">
        <fragment
            android:id="@+id/actionHome"
            android:name="com.sample.demo.fragments.Home"
            android:label="fragment_home"
            tools:layout="@layout/fragment_home">
            <action
                android:id="@+id/toExplore"
                app:destination="@id/actionExplore" />
        </fragment>
        <fragment
            android:id="@+id/actionExplore"
            android:name="com.sample.demo.fragments.Explore"
            android:label="fragment_explore"
            tools:layout="@layout/fragment_explore" />
        <fragment
            android:id="@+id/actionBusiness"
            android:name="com.sample.demo.fragments.Business"
            android:label="fragment_business"
            tools:layout="@layout/fragment_business" />
        <fragment
            android:id="@+id/actionProfile"
            android:name="com.sample.demo.fragments.Profile"
            android:label="fragment_profile"
            tools:layout="@layout/fragment_profile" />
    </navigation>
Every Navigation Fragment ID and bottom navigation menu item id should be same. For Example here
 <fragment
  android:id="@+id/actionBusiness"
 android:name="com.sample.demo.fragments.Business"
                android:label="fragment_business"
                tools:layout="@layout/fragment_business" />
Below bottom Navigation menu navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/actionExplore"
        android:icon="@drawable/ic_search_24dp"
        android:title="@string/explore" />
    <item
        android:id="@+id/actionBusiness"
        android:icon="@drawable/ic_business_24dp"
        android:title="@string/business" />
    <item
        android:id="@+id/actionProfile"
        android:icon="@drawable/ic_profile_24dp"
        android:title="@string/profile" />
</menu>
Set the nav_graph.xml to palceholder fragment in activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/gradient_bg"
    android:focusable="true"
    android:focusableInTouchMode="true"
    tools:context=".MainActivity"
    tools:layout_editor_absoluteY="25dp">
    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="@color/semi_grey"
        app:itemIconTint="@drawable/bottom_bar_nav_item"
        app:itemTextColor="@drawable/bottom_bar_nav_item"
        app:labelVisibilityMode="labeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/navigation" />
    <include
        android:id="@+id/appBarLayout"
        layout="@layout/app_bar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <fragment
        android:id="@+id/mainNavigationFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:paddingBottom="@dimen/activity_horizontal_margin"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
        app:navGraph="@navigation/nav_graph" />
</android.support.constraint.ConstraintLayout>
Mapping the Navigation Graph into fragment here app:navGraph="@navigation/nav_graph"
After that implement navigation graph and bottomNavigation component in MainActivity.java
 BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        NavController navController = Navigation.findNavController(this, R.id.mainNavigationFragment);
        NavigationUI.setupWithNavController(navigation, navController); 
Cheers!!!
 
    
    i think you dont need viewPager and you can implement with bottom navigation and navigation component .only you have to create navigations for any of destination in bottom navigation for example you have 4 destination(home_page,search_page,card_page,profile_page) in navigation bottom you need create 4 navigation (home_nav,search_nav,card_nav,profile_nav).
and set destinations for menu_bottom_navigatoin. so you can save navigate and stack state of any tab. if you use mvvm and viewmodel you can hold data from server and prevent reload for data . for more you can check this code is awesome . https://github.com/android/architecture-components-samples/tree/main/NavigationAdvancedSample
