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.xml
OnNavigationItemSelectedListener
to BottomNavigationView
in MainActivity.kt
NavHostFragment
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://mcmap.net/q/57492/-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!!!
© 2022 - 2024 — McMap. All rights reserved.