Tutorials for learning Navigation Components with simple set up, top menus, passing arguments via navigation graphs and combining them with different Material Design widgets such as BottomNavigationView, Toolbar, ViewPager2, TabLayout and dynamic feature module navigation with DynamicNavHostFragment.
Covers basic BottomNavigationView and ViewPager usage without any navigatiom components. This is a little bit like warm up before moving to more complex ones including navigation with ViewPager2
Covers how to use create navigation graph nav_graph.xml inside navigation folder, and NavHostFragmentin activity_main.xml layout file.
One important note to add is navigation uses FragmentTransaction.replace() to navigate next fragment inside specified NavHostFragment
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
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="true"
app:navGraph="@navigation/nav_graph" />
There are multiple ways to navigate from one fragment to another using a NavController, for instance
buttonCenter?.setOnClickListener {
val options = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
findNavController().navigate(R.id.middle1_dest, null, options)
}
Check out this tutorial if you wish to get familiar with basic consepts, animation and navigating with popUpToand popUpToInclusive
Uses seperate and nested fragments with each it's own nav graph.
nav_graph is the main graph for navigation which can directly go to CameraFragment or nav_graph_dashboard or nav_graph_home
Nested graphs are part of the same navHostFragment?.childFragmentManager
Tutorial1-3Navigation-NestedNavHost
Covers how nested graphs with their own back stack or NavHostFragment work. You can check out both main and home childFragmentManager back stack entry and fragment counts by examining Toast or log messages.
Main graph back stack is controlled by NavHostFragment.getChildFragmentManager
When a nested navigation graph or NavHostFragment added also it's back stack is retrieved
using childFragmentManager.
When on nested graph back button navigates from that back stack to current entry position on main graph.
HomeNavHostFragment should be created, in this tutorial we create it by setting it by
initial destination
val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
// Get NavHostFragment
val navHostFragment =
childFragmentManager.findFragmentById(nestedNavHostFragmentId)
// ChildFragmentManager of the current NavHostFragment
val navHostChildFragmentManager = navHostFragment?.childFragmentManager
// Check if it's the root of nested fragments in this navhosts
if (navController?.currentDestination?.id == navController?.graph?.startDestination) {
/*
Disable this callback because calls OnBackPressedDispatcher
gets invoked calls this callback gets stuck in a loop
*/
isEnabled = false
requireActivity().onBackPressed()
isEnabled = true
} else if (isVisible) {
navController?.navigateUp()
}
}
}
Back navigation does NOT work for the first fragment in back stack of HomeNavHostFragment for this example, because
if (navController!!.currentDestination == null || navController!!.currentDestination!!.id == navController!!.graph.startDestination) {
navController?.navigate(R.id.homeFragment1)
}
and start destination is HomeNavHostFragmentitself while it's navigation to HomeFragment1on last back press to move back from current sub back stack
Change app:startDestination="@id/home_dest" to app:startDestination="@id/homeFragment1" to solve back press issue for HomeNavHostFragment, it's just set to demonstrate how start destination change back press.
<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_home"
app:startDestination="@id/home_dest">
<fragment
android:id="@+id/home_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.navhost.HomeNavHostFragment"
android:label="HomeHost"
tools:layout="@layout/fragment_navhost_home" />
<fragment
android:id="@+id/homeFragment1"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment1"
android:label="HomeFragment1"
tools:layout="@layout/fragment_home1">
</fragment>
</navigation>
Navigation Architecture
MainActivity (Appbar + Toolbar)
|- MainNavHost
|
| FragmentViewPagerContainer(ViewPager2 + TabLayout)
|- HomeFragment1 -> HomeFragment2 -> HomeFragment3
|- DashboardFragment1 -> DashboardFragment2 -> DashboardFragment3
Covers how to create a ViewPager2with navigation in main back stack, in this example ViewPager2pages do not have their own back stack. It's covered in tutorials Tutorial6-2.
Navigation Architecture
Same as previous tutorial with only one difference data binding is used in layouts.
Data binding that is not null(or non-nullable) after Fragment.onDestroyView when ViewPager2is inside a fragment causing leak canary to show data binding related MEMORY LEAK for this fragment when used in ```ViewPager2``. Also you need to set adapter of ViewPager2 either to prevent memory leaks, and another one is due to TabLayouts which is covered in later sections. Check out this stackoverflow question for more details.
Navigation Architecture
MainActivity (Appbar + Toolbar)
|- MainNavHost
|
|- ViewPagerContainerFragment(ViewPager2 + TabLayout)
| |- HomeNavHostFragment
| | |- HF1 -> HF2 -> HF3
| |
| |- DashboardNavHostFragment
| | |- DF1 -> DF2 -> DF3
| |
| |- NotificationHostFragment
| | |- NF1 -> NF2 -> NF3
| |
| |-LoginFragment1
|
|- LoginFragment1 -> LoginFragment2
Covers ViewPager2 each with it's own back stack using fragments that have their own nested
NavHostFragments to have their own back stack, back/forth navigation.
This tutorial has very important aspects for ViewPager2 navigation
-
Creating
NavHostFragmentfor each page and can navigate inside them, each page has it's own nav graph.in each layout file
NavHostFragmentinside is retrieved usingval nestedNavHostFragment = childFragmentManager.findFragmentById(nestedNavHostFragmentId) as? NavHostFragment navController = nestedNavHostFragment?.navController`HomeNavHostFragmentuses the first fragment that is displayed on screen **HomeFragment1 whileDashboardNavHostFragmentuses graph with itself as start destination so it should check for theNavController.getCurrentDestination()to navigate to it when device rotatedLoginFragment1is added to main graph, because of that appbar back navigation only works with theViewPagerContainerFragment'sNavController -
How to use back navigation with
OnBackPressedCallback, there is an alternative and more simple way to handle back navigation forViewPager2but this also a way to keep in mind if more customization is required. If you do not handle back navigation Activity's back press gets called and application starts from onCreate. -
Checking out memory leaks with data binding, ViewPager2 adapter and lifecylce.
-
You should set data binding to null or you will get memory leaks for this ViewPager2 which is itself also inside a fragment
-
You should set ViePager2's adapter to null in
onDestroyView -
🔥 You should use
ChildFragmentStateAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle), not the one that takesFragmentas parameter. And use view's lifecycle instead of setting Fragment's lifecycle.
viewPager.adapter = ChildFragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) -
Navigation Architecture
MainActivity(Appbar + Toolbar + TabLayout + ViewPager2)
|
|- HomeNavHostFragment
| |- HF1 -> HF2 -> HF3
|
|- DashboardNavHostFragment
| |- DF1 -> DF2 -> DF3
|
|- NotificationHostFragment
|- NF1 -> NF2 -> NF3
In this tutorial MainActivity has it's appbar that navigation is controlled using the [NavController] retrieved from [NavHostFragment] via [LiveData]
Issue with rotation, when device rotated [ActivityFragmentStateAdapter.createFragment] method is not called and it's not possible to access [NavController] of newly created fragments. If you do not wish to have a rotatable app
you can use live data or ViewModel to get current NavController to change appbar title and get other
properties of NavController. LiveData is observed in MainActivity to set appbar title
Navigation Architecture
MainActivity
|- MainNavHost
|
|- ViewPagerContainerFragment(ViewPager)
|
|- HomeNavHostFragment(Appbar + Toolbar)
| |- HF1 -> HF2 -> HF3
|
|- DashboardNavHostFragment(Appbar Toolbar)
| |- DF1 -> DF2 -> DF3
|
|- NotificationHostFragment(Appbar Toolbar)
|- NF1 -> NF2 -> NF3
In this tutorial each NavHostFragment has it's own toolbar
They can navigate back with back arrow when navigated to an inner fragment of ViewPager
Using FragmentStateAdapter.registerFragmentTransactionCallback with FragmentStateAdapter solves back navigation instead of using OnBackPressedCallback.handleOnBackPressed in every NavHostFragment as answered here
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)
}
})
}
Should set app:defaultNavHost="true" for [NavHostFragment] for this to work
Navigation Architecture
MainActivity
|- MainNavHost
|
|- ParenNavHost((Appbar + Toolbar)
|
|- ViewPagerContainerFragment(ViewPager2)
| |
| |- HomeNavHostFragment(Appbar + Toolbar)
| | |- HF1 -> HF2 -> HF3
| |
| |- DashboardNavHostFragment(Appbar + Toolbar)
| | |- DF1 -> DF2 -> DF3
| |
| |- NotificationHostFragment(Appbar + Toolbar)
| | |- NF1 -> NF2 -> NF3
| |
| |-LoginFragment1
|
|- LoginFragment1 -> LoginFragment2
In this tutorial each NavHostFragment has it's own toolbar, also ParentNavHostFragment has it's own toolbar.
LoginFragment2 in this example is added to back stack of ParentNavHostFragment because of that it does not have any association with toolbar in ViewPagerContainerFragment
ParentNavHostFragment's role is to have it's own Appbar to contain login fragments and navigate through them using Appbar. Without ParentNavHostFragment we navigate to LoginFragment2 that has no Appbar.
Visibility of ParentNavHostFragment is changed via liveData of AppbarViewModel
However, there is an issue with setting visibility and dimensions of ViewPager2 when visibility of Appbar parent toolbar changes
Navigation Architecture
MainActivity
|- MainNavHost
|
|- ParenNavHost(Appbar + Toolbar) Here because we wish to have toolbar inside Fragment
|
|- ViewPagerContainerFragment(TabLayout + ViewPager2)
| |
| |- HomeNavHostFragment
| | |- HF1 -> HF2 -> HF3
| |
| |- DashboardNavHostFragment
| | |- DF1 -> DF2 -> DF3
| |
| |- NotificationHostFragment
| | |- NF1 -> NF2 -> NF3
| |
| |-LoginFragment1
|
|- LoginFragment1 -> LoginFragment2
In this tutorial only ParentNavHostFragment has Appbar and Toolbar, navigation of individual
NavHostFragments is done via AppbarViewModel.currentNavController liveData which
returns the visible [NavController] of current [NavHostFragment] due to setting it in [NavHostFragment.onResume]
ParentNavHostFragment's role is to have it's own Appbar to contain login fragments and navigate through them using Appbar. Without ParentNavHostFragment we navigate to LoginFragment2 that has no Appbar if it's inside ViewPagerContainerFragment.
It can be done by putting Appbar to MainActivity but purpose here is to put
Appbar + Toolbar inside a fragment to be able to use with [BottomNavigationView] for instance





