The Navigation component is a library that can manage complex navigation, transition animation, deep linking, and compile-time checked argument passing between the screens in your app.
To implement
Step 1
dependencies {
...
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
...
}
Step 2
Add a navigation graph to the project
Create a new resource file - say call it main_navigation_graph and select Navigation as the Resource type.
Android studio will automatically create a navigation folder into res folder if it doesn’t exists.
This is how navigation graph will look like
<?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"
android:id="@+id/navigation">
</navigation>
Step 3
Create the NavHostFragment
A navigation host fragment acts as a host for the fragments in a navigation graph. The navigation host Fragment is usually named NavHostFragment.
As the user moves between destinations defined in the navigation graph, the navigation host Fragment swaps fragments in and out as necessary. The Fragment also creates and manages the appropriate Fragment back stack.
<!-- The NavHostFragment within the activity_main layout -->
<fragment
android:id="@+id/myNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/main_navigation_graph"
app:defaultNavHost="true" />
Notice that we’re specifying navGraph and defaultNavHost(This makes this navigation host the default host and will intercept the system Back button) here.
Understand that every navigation graph needs a host(A start point)
Step 4
Let’s read the below navigation graph (main_navigation_graph.xml) and connect them with an action
<?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/navigation"
app:startDestination="@id/titleFragment2">
<fragment
android:id="@+id/titleFragment2"
android:name="com.example.android.navigation.TitleFragment"
android:label="fragment_title"
tools:layout="@layout/fragment_title" >
<action
android:id="@+id/action_titleFragment2_to_gameFragment"
app:destination="@id/gameFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@id/titleFragment2" />
</fragment>
<fragment
android:id="@+id/gameFragment"
android:name="com.example.android.navigation.GameFragment"
tools:layout="@layout/fragment_game"
android:label="GameFragment" >
<action
android:id="@+id/action_gameFragment_to_gameOverFragment"
app:destination="@id/gameOverFragment"
app:popUpTo="@id/gameFragment"
app:popUpToInclusive="true"/>
<action
android:id="@+id/action_gameFragment_to_gameWonFragment"
app:destination="@id/gameWonFragment"
app:popUpTo="@id/titleFragment2" />
</fragment>
<fragment
android:id="@+id/gameOverFragment"
android:name="com.example.android.navigation.GameOverFragment"
android:label="fragment_game_over"
tools:layout="@layout/fragment_game_over" >
<action
android:id="@+id/action_gameOverFragment_to_gameFragment"
app:destination="@id/gameFragment"
app:popUpTo="@id/titleFragment2" />
</fragment>
<fragment
android:id="@+id/gameWonFragment"
android:name="com.example.android.navigation.GameWonFragment"
android:label="fragment_game_won"
tools:layout="@layout/fragment_game_won" >
<action
android:id="@+id/action_gameWonFragment_to_gameFragment"
app:destination="@id/gameFragment"
app:popUpTo="@id/titleFragment2" />
<argument
android:name="numQuestions"
app:argType="integer" />
<argument
android:name="numCorrect"
app:argType="integer" />
</fragment>
<fragment
android:id="@+id/aboutFragment"
android:name="com.example.android.navigation.AboutFragment"
android:label="fragment_about"
tools:layout="@layout/fragment_about" />
</navigation>
Mentioning startDestination is important. This is automatically done for you when you add your first destination in navigation graph using the add destination button in DESIGN tab.
Every destination a fragment can reach to is an action inside that fragment.
Each action also describes the back button behaviour using popUpTo and popUpToInclusive attributes.
- The popUpTo attribute of an action “pops up” the back stack to a given destination before navigating. (Destinations are removed from the back stack.)
- If the popUpToInclusive attribute is false or is not set, popUpTo removes destinations up to the specified destination, but leaves the specified destination in the back stack.
- If popUpToInclusive is set to true, the popUpTo attribute removes all destinations up to and including the given destination from the back stack.
- If popUpToInclusive is true and popUpTo is set to the app’s starting location, the action removes all app destinations from the back stack. The Back button takes the user all the way out of the app.
Label is what is displayed on the app bar. We will add support for Up button in next steps.
Ignore the arguments tag for now. However, these are the arguments gameWonFragment expects.
Step 5
On a button click, when you need to navigate
view.findNavController().navigate(R.id.action_gameFragment_to_gameWonFragment)
Step 6 -> Add an Up button in the app bar
This support is added inside the activity
The Up button navigates within the app, based on the hierarchical relationships between screens. The Up button never navigates the user out of the app.
The navigation components include a UI library called NavigationUI. The Navigation component includes a NavigationUI class. This class contains static methods that manage navigation with the top app bar, the navigation drawer, and bottom navigation. The navigation controller integrates with the app bar to implement the behavior of the Up button, so you don’t have to do it yourself.
Do this in Activity’s onCreate and you will start seeing the up button.
val navController = findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController)
To handle clicks on up button, override onSupportNavigateUp() in the Activity
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return navController.navigateUp()
}
Step 7 -> Add an options menu in app bar
Create a menu and add item in the menu. Keep the id of the menu
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/aboutFragment"
android:title="About" />
</menu>
We’re adding this support inside the fragment. We don’t want to display this menu items on all screens.
So in fragment’s onCreateView
setHasOptionsMenu(true)
This will give a callback in onCreateOptionsMenu function. So override it & inflate the menu
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.options_menu_main, menu)
}
Now this will inflate the menu. To handle the clicks, override onOptionsItemSelected
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return NavigationUI.onNavDestinationSelected(item, requireView().findNavController())
|| super.onOptionsItemSelected(item)
}
requireView() inside a frament returns root view of the fragment.
Make sure that the ID of the menu item that you just added is exactly the same as the ID of the AboutFragment that you added in the navigation graph. This will make the code for the onClick handler much simpler.
This is exactly how NavigationUI.onNavDestinationSelected(item, requireView().findNavController()) inside onOptionsItemSelected is able to handle the click automatically.
Step 10 -> Add the navigation drawer
The navigation drawer is part of the Material Components for Android library, or Material library. You use the Material library to implement patterns that are part of Google’s Material Design guidelines.
So in build.gradle
implementation "com.google.android.material:material:$version"
Now create the drawer menu and drawer header layout
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/aboutFragment"
android:title="About" />
</menu>
Now add drawerLayout and navigationView inside activity. This is the order.
<?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.drawerlayout.widget.DrawerLayout
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/myNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation"
/>
</LinearLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:itemTextColor="@color/colorPrimary"
app:itemIconTint="@color/colorAccent"
app:menu="@menu/navdrawer_menu"
app:headerLayout="@layout/nav_header" />
</androidx.drawerlayout.widget.DrawerLayout>
</layout>
Note: If you use the same ID for the menu item as for the destination Fragment, you don’t need to write any code at all to implement the onClick listener!
Now to display your Navigation drawer, Add this on bottom of onCreate.
NavigationUI.setupWithNavController(binding.navView, navController)
At this moment the swipe starts working. However to handle clicks, do this change
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@Suppress("UNUSED_VARIABLE")
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
drawerLayout = binding.drawerLayout
val navController = findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
NavigationUI.setupWithNavController(binding.navView, navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return NavigationUI.navigateUp(navController, drawerLayout)
}
Notice that setupActionBarWithNavController changed from
NavigationUI.setupActionBarWithNavController(this, navController)
//to this
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
And onSupportNavigateUp Change from
//this
navController.navigateUp()
//to this
NavigationUI.navigateUp(navController, drawerLayout)
BONUS
Your code usually needs to pass parameters from one Fragment to another. To prevent bugs in these transactions and make them type-safe, you use a Gradle plugin called Safe Args. The plugin generates NavDirection classes, and you add these classes to your code.
The purpose of Safeargs is to remove errors(Type mismatch errors & Missing key errors) at compile time rather than at run time and thus achieve type safety.
Let’s see how.
Add this at project level build.gradle
// Adding the safe-args dependency to the project Gradle file
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
}
Then apply plugin in app level build.gradle
// Adding the apply plugin statement for safeargs
apply plugin: 'androidx.navigation.safeargs'
Rebuild the project and this will generate nav direction class for you. To see the generated files, explore the generatedJava folder in the Project > Android pane.
Now remember we included arguments tags in navigation graph above? They work with these nav directions classes.
So instead of passing the action id (R.id.actionId), you do this :
// Using directions to navigate to the GameWonFragment
view.findNavController()
.navigate(GameFragmentDirections.actionGameFragmentToGameWonFragment(3, 3))
And this your fragment is expecting 2 arguments, you pass the arguments else you get compile time error.
Now to retrieve this in GameWonFragment :
val args = GameWonFragmentsArgs.fromBundle(requireArguments())
Toast.makeText(context, "NumCorrect: ${args.numCorrect}, NumQuestions: ${args.numQuestions}", Toast.LENGTH_LONG).show()
Read more about Safe Args here:
https://developer.android.com/topic/libraries/architecture/navigation/navigation-pass-data#Safe-args
Thanks for reading!