The Navigation Architecture Component simplifies implementing navigation, while also helping you visualize your app's navigation flow. The library provides a number of benefits, including:
In this codelab, you will work with the sample app seen below (you may need to right click this image and choose "Open image in new tab" otherwise it may be too large to view):
All the activities and fragments have already been created for you. You will use the Navigation Component to connect them and in doing so, implement the following:
Get the Code
You should download the project as a Zip file from Blackboard.
Make sure you are using Android Studio 3.3 or higher. This is required for the Android Studio navigation tooling.
If you need to download a recent version of Android Studio, you can do so here.
The Navigation Component consists of three key parts, working together in harmony. They are:
NavHostFragment
as you move through a navigation graph.NavController
object, telling it where you want to go or what path you want to take in your Navigation Graph. The NavController
will then show the appropriate destination in the NavHostFragment
.That's the basic idea. Let's see what this looks like in practice, starting with the new Navigation Graph resource.
The Navigation Component introduces the concept of a destination. A destination is any place you can navigate to in your app, usually a fragment or an activity. These are supported out of the box, but you can also make your own custom destination types if needed.
A navigation graph is a new resource type that defines all the possible paths a user can take through an app. It shows visually all the destinations that can be reached from a given destination. Android Studio displays the graph in its Navigation Editor. Here's part of the starting navigation graph you'll create for your app:
res/navigation/mobile_navigation.xml
All of the changes you make in the graphical Navigation Editor change the underlying XML file, similar to the way the Layout Editor modifies the layout XML.
Click the Code tab and you will see the XML source of the file:
Notice:
<navigation>
is the root node of every navigation graph.<navigation>
contains one or more destinations, represented by <activity>
or <fragment>
elements.app:startDestination
is an attribute that specifies the destination that is launched by default when the user first opens the app.Let's take a look at a fragment destination:
<fragment
android:id="@+id/flow_step_one_dest"
android:name="uk.aston.androidnavigation.FlowStepFragment"
tools:layout="@layout/flow_step_one_fragment">
<argument
.../>
<action
android:id="@+id/next_action"
app:destination="@id/flow_step_two_dest"></action>
</fragment>
Notice:
android:id
defines an ID for the fragment that you can use to reference the destination elsewhere in this XML and your code.android:name
declares the fully qualified class name of the fragment to instantiate when you navigate to that destination.tools:layout
specifies what layout should be shown in the graphical editor.<fragment>
tags also contain <action>
, <argument>
, and <deepLink>
, all of which we'll cover later.The sample app starts with a few destinations in the graph. In this step, you'll add a brand new destination! You must add a destination to the navigation graph before you can navigate to it.
You should compare the code you write to the included commented-out code.
res/navigation/mobile_navigation.xml
, and click the Design tab.<fragment>
element to your mobile_navigation.xsml
<fragment
android:id="@+id/settings_dest"
android:name="com.example.android.codelabs.navigation.SettingsFragment"
android:label="settings_fragment"
tools:layout="@layout/settings_fragment" />
Right now you have this awesome navigation graph, but you're not actually using it to navigate.
The Navigation component follows the guidance outlined in the Principles of Navigation. The Principles of Navigation recommend you use activities as entry points for your app. Activities will also contain global navigation, such as the bottom nav,
In comparison, fragments will be the actual destination-specific layouts.
To get this all to work, you need to modify your activity layouts to contain a special widget called a NavHostFragment. A NavHostFragment
swaps different fragment destinations in and out as you navigate through the navigation graph.
A simple layout supporting navigation similar to the picture above looks like this. An example of this code can be found in res/layout-470dp/navigation_activity.xml
:
<?xml version="1.0" encoding="utf-8"?>
<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="uk.aston.androidnavigation.MainActivity">
<androidx.appcompat.widget.Toolbar
... />
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
... />
</LinearLayout>
Notice:
android:name="androidx.navigation.fragment.NavHostFragment"
and app:defaultNavHost="true"
connect the system back button to the NavHostFragment
app:navGraph="@navigation/mobile_navigation"
associates the NavHostFragment
with a navigation graph. This navigation graph specifies all the destinations the user can navigate to, in this NavHostFragment
.Finally, when a user does something like clicking a button, you need to trigger a navigate command. A special class called the NavController is what triggers the fragment swaps in the NavHostFragment
.
// Command to navigate to flow_step_one_dest
NavHostFragment.findNavController(HomeFragment.this)
.navigate(R.id.flow_step_one_dest, null);
Note that you pass in either a destination or action ID to navigate. These are the IDs defined in the navigation graph XML. This is an example of passing in a destination ID.
NavController
is powerful because when you call methods like navigate()
or popBackStack()
, it translates these commands into the appropriate framework operations based on the type of destination you are navigating to or from. For example, when you call navigate()
with an activity destination, the NavController
calls startActivity()
on your behalf.
There are a few ways to get a NavController
object associated with your NavHostFragment. In Java, it's recommended you use one of the following methods to retrieve a NavController
:
It's your turn to navigate using NavController. You'll hook up the Navigate To Destination button to navigate to the flow_step_one_dest
destination (which is a destination that is a FlowStepFragment
):
navigate_destination_button
in onViewCreated()
HomeFragment.java
Button button = view.findViewById(R.id.navigate_destination_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NavHostFragment.findNavController(HomeFragment.this)
.navigate(R.id.flow_step_one_dest, null);
}
});
flow_step_one_dest
destination.Each navigate()
call has a not very exciting default transition associated with it, as seen below (right click and open in a new tab if this appears too large):
The default transition, as well as other attributes associated with the call, can be overridden by including a set of NavOptions
. NavOptions
uses a Builder
pattern which allows you to override and set only the options you need.
For animated transitions, you can define XML animation resources in the anim resource folder and then use those animations for transitions. Some examples are included in the app code:
Update the code so that pressing the Navigate To Destination button shows a custom transition animation.
NavOptions
and pass it into the navigate()
call to navigate_destination_button
final NavOptions options = new NavOptions.Builder()
.setEnterAnim(R.anim.slide_in_right)
.setExitAnim(R.anim.slide_out_left)
.setPopEnterAnim(R.anim.slide_in_left)
.setPopExitAnim(R.anim.slide_out_right)
.build();
Button button = view.findViewById(R.id.navigate_destination_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NavHostFragment.findNavController(HomeFragment.this)
.navigate(R.id.flow_step_one_dest, null, options);
}
});
The navigation system also allows you to navigate via actions. As previously mentioned, the lines shown in the navigation graph are visual representations of actions.
Navigation by actions has the following benefits over navigation by destination:
flow_step_one_dest
and flow_step_two_dest
:<fragment
android:id="@+id/flow_step_one_dest"
android:name="uk.aston.androidnavigation.FlowStepFragment"
tools:layout="@layout/flow_step_one_fragment">
<argument
.../>
<action
android:id="@+id/next_action"
app:destination="@id/flow_step_two_dest"></action>
</fragment>
<fragment
android:id="@+id/flow_step_two_dest"
android:name="uk.aston.androidnavigation.FlowStepFragment"
tools:layout="@layout/flow_step_two_fragment">
<!-- ...removed for simplicity --->
</fragment>
Notice:
"next_action"
flow_step_two_dest
to home_dest
:<fragment
android:id="@+id/home_dest"
android:name="uk.aston.androidnavigation.HomeFragment"
android:label="@string/home"
tools:layout="@layout/home_fragment">
</fragment>
<fragment
android:id="@+id/flow_step_two_dest"
android:name="uk.aston.androidnavigation.FlowStepFragment"
tools:layout="@layout/flow_step_two_fragment">
<argument
.../>
<action
android:id="@+id/next_action"
app:popUpTo="@id/home_dest"></action>
</fragment>
Notice:
flow_step_two_dest
to home_dest
. You can navigate using the next_action
id from either flow_step_one_dest
or flow_step_two_dest
. This is an example of how actions can provide a level of abstraction and can navigate you somewhere different depending on context.popUpTo
attribute is used - this action will pop fragments off of the back-stack until you reach home_dest
Time to hook up the Navigate with Action button so that it lives up to its name!
mobile_navigation.xml
file in Design modehome_dest
to flow_step_one_dest
:next_action
action under the home_dest
destination:<fragment
android:id="@+id/home_dest"
android:name="uk.aston.androidnavigation.HomeFragment"
android:label="@string/home"
tools:layout="@layout/home_fragment">
<action
android:id="@+id/next_action"
app:destination="@id/flow_step_one_dest"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
HomeFragment.java
navigate_action_button
HomeFragment.javaButton navActionButton = view.findViewById(R.id.navigate_action_button);
navActionButton.setOnClickListener(
Navigation.createNavigateOnClickListener(R.id.next_action, null)
);
The navigation component has a Gradle plugin, called safe args, that generates simple object and builder classes for type-safe access to arguments specified for destinations and actions.
Safe args allows you to get rid of code like this when passing values between destinations:
String username = getArguments().getString("usernameKey");
And, instead, replace it with code that has generated setters and getters.
String username = args.getUsername();
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
def nav_version = "2.3.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
app/build.gradle
file and edit it to apply the plugin (add the second line below):apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs"
android {
...
}
mobile_navigation.xml
, and notice how arguments are defined in the flow_step_one_dest
destination. <fragment
android:id="@+id/flow_step_one_dest"
android:name="uk.aston.androidnavigation.FlowStepFragment"
tools:layout="@layout/flow_step_one_fragment">
<argument
android:name="flowStepNumber"
app:argType="integer"
android:defaultValue="1"/>
<action ...></action>
</fragment>
Using the tag, safeargs generates a class called FlowStepFragmentArgs
which you will find in the java (generated)
folder.
Since the XML includes an argument called flowStepNumber
, specified by android:name="flowStepNumber"
, the generated class FlowStepFragmentArgs
will maintain a flowStepNumber
whose value can be accessed with getters and setters called getFlowStepNumber()
and setFlowStepNumber(int flowStepNumber)
.
FlowStepFragment.java
int flowStepNumber = getArguments().getInt("flowStepNumber");
This old-style code is not type-safe. It's better to use safe args.FlowStepFragment
to use the code generated class FlowStepFragmentArgs
. This will get the FlowStepFragment
arguments in a type-safe manner:FlowStepFragmentArgs args = FlowStepFragmentArgs.fromBundle(getArguments());
int flowStepNumber = args.getFlowStepNumber();
You can also use safe args to navigate in a type safe way, with or without adding arguments. You do this using the generated Directions classes.
Directions classes are generated for every distinct destination with actions. The Directions class includes methods for every action a destination has.
For example, the navigate_action_button
click listener in HomeFragment.java
could be changed to:
HomeFragment.java
Button actionButton = view.findViewById(R.id.navigate_action_button);
actionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int flowStepNumberArg = 1;
HomeFragmentDirections.NextAction action =
HomeFragmentDirections.nextAction()
.setFlowStepNumber(flowStepNumberArg);
NavHostFragment.findNavController(HomeFragment.this)
.navigate(action);
}
});
NavigationUI and navigation-ui-ktx
The Navigation Components include a NavigationUI class and the navigation-ui-ktx kotlin extensions. NavigationUI
has static methods that associate menu items with navigation destinations, and navigation-ui-ktx
is a set of extension functions that do the same. If NavigationUI
finds a menu item with the same ID as a destination on the current graph, it configures the menu item to navigate to that destination.
One of the easiest ways to use NavigationUI
is to have it simplify option menu setup. In particular, NavigationUI
simplifies handling the onOptionsItemSelected
callback.
MainActivity.java
overflow_menu
in onCreateOptionsMenu
res/menu/overflow_menu.xml
settings_dest
overflow_menu.xml<item
android:id="@+id/settings_dest"
android:icon="@drawable/ic_settings"
android:menuCategory="secondary"
android:title="@string/settings" />
NavigationUI
handle onOptionsItemSelected
with the onNavDestinationSelected
helper method. We need to pass the MenuItem
and the NavController
as parameters. If the menu item is not meant to navigate, handle with super.onOptionsItemSelected
. Here is the code: @Override
public boolean onOptionsItemSelected(MenuItem item) {
return NavigationUI.onNavDestinationSelected(item,
Navigation.findNavController(this, R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item);
}
The above example shows how to find the NavController
from within an Activity
.ActionBar
menu that navigates to the SettingsFragment
.The code already contains the XML layout code for implementing bottom navigation, which is why you see the bottom navigation bar. But it doesn't navigate anywhere.
res/layout/navigation_activity/navigation_activity.xml (h470dp)
and click the Code tabbottom_nav_menu.xml
navigation_activity.xml (h470dp)<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_nav_menu" />
res/menu/bottom_nav_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/home_dest"
android:icon="@drawable/ic_home"
android:title="@string/home" />
<item
android:id="@id/deeplink_dest"
android:icon="@drawable/ic_android"
android:title="@string/deeplink" />
</menu>
Let's make the bottom navigation actually do something using NavigationUI
.setupBottomNavMenu
method using setupWithNavController(bottomNavigationView: BottomNavigationView, navController: NavController) private void setupBottomNavMenu(NavController navController) {
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav_view);
NavigationUI.setupWithNavController(bottomNav, navController);
}
Finally, let's use NavigationUI
to configure the side navigation and navigation drawer, including handling the ActionBar
and proper up navigation. You'll see this if you've got a large enough screen or if the screen's too short for bottom navigation.
First observe how the proper layout XML code is already in the app.
navigation_activity.xml
and navigation_activity.xml (w960dp)
NavigationView
connected to nav_drawer_menu
. In the tablet version (w960dp) the NavigationView
is always on screen. On smaller devices the NavigationView
is nested within a DrawerLayout
.MainActivity.java
setupNavigationMenu
method using setupWithNavController
(navigationView: NavigationView, navController: NavController). Notice how this version of the method takes a NavigationView
and not a BottomNavigationView
.MainActivity.java
private void setupNavigationMenu(NavController navController) {
NavigationView sideNavView = findViewById(R.id.nav_view);
NavigationUI.setupWithNavController(sideNavView, navController);
}
Now the navigation view menu will show on the screen, but it will not affect the ActionBar
.
Setting up the ActionBar
requires creating an instance of AppBarConfiguration
. The purpose of AppBarConfiguration
is to specify the configuration options you want for your toolbars, collapsing toolbars, and action bars. Configuration options include whether the bar must handle a drawer layout and which destinations are considered top-level destinations.
Top-level destinations are the root-level destinations of your app. These destinations do not display an "up" button in the app bar, and they display the drawer icon if the destination uses a drawer layout.
AppBarConfiguration
by passing in a set of top-level destination IDs and the drawer layout.DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
if (drawerLayout != null) {
appBarConfiguration = new AppBarConfiguration.Builder(R.id.home_dest, R.id.deeplink_dest).setOpenableLayout(drawerLayout).build();
} else {
appBarConfiguration = new AppBarConfiguration.Builder(R.id.home_dest, R.id.deeplink_dest).build();
}
Now that you have an AppBarConfiguration
, you can call NavigationUI.setupActionBarWithNavController
. This will do the following:
private void setupActionBar(NavController navController, AppBarConfiguration appBarConfiguration) {
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
}
You should also have NavigationUI
handle what happens when the Up button is pressed.onSupportNavigationUp
and call NavigationUI.navigateUp
, using the same AppBarConfiguration.@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(Navigation.findNavController(this, R.id.my_nav_host_fragment), appBarConfiguration)
|| super.onSupportNavigateUp();
}
Adding new destinations to a NavigationView is easy. Once you have the navigation drawer working with up and back navigation, you just need to add the new menu item.
menu/nav_drawer_menu.xml
settings_dest
nav_drawer_menu.xml<item
android:id="@+id/settings_dest"
android:icon="@drawable/ic_settings"
android:title="@string/settings" />
Now your navigation drawers shows the Settings screen as a destination. Good work!
Navigation components also include deep link support. Deep links are a way to jump into the middle of your app's navigation, whether that's from an actual URL link or a pending intent from a notification.
One benefit of using the navigation library to handle deep links is that it ensures users start on the right destination with the appropriate back stack from other entry points such as app widgets, notifications, or web links (covered in the next step).
Navigation provides a NavDeepLinkBuilder
class to construct a PendingIntent
that will take the user to a specific destination.
We'll use the NavDeepLinkBuilder
to hook up an app widget to a destination.
DeepLinkAppWidgetProvider.java
PendingIntent
constructed with NavDeepLinkBuilder
:val args = Bundle()
args.putString("myarg", "From Widget");
val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.mobile_navigation)
.setDestination(R.id.deeplink_dest)
.setArguments(args)
.createPendingIntent()
remoteViews.setOnClickPendingIntent(R.id.deep_link_button, pendingIntent)
Notice:
setGraph
includes the navigation graph.setDestination
specifies where the link goes to.setArguments
includes any arguments you want to pass into your deep link.Tap and hold
Scroll down to find widget
When you are finished you'll have a deep link widget:
"From Widget"
at the top since that is the argument you passed in DeepLinkAppWidgetProvider
.The backstack for a deep link is determined using the navigation graph you pass in. If the explicit Activity
you've chosen has a parent activity, those parent Activities
are also included.
The backstack is generated using the destinations specified with app:startDestination
. In this app we only have one activity and one level of navigation, so the backstack will take you to the home_dest
destination.
More complicated navigation can include nested navigation graphs. The app:startDestination
at each level of the nested graphs determines the backstack. For more information on deep links and nested graphs, check out the Principles of Navigation.
One of the most common uses of a deep link is to allow a web link to open an activity in your app. Traditionally you would use an intent-filter and associate a URL with the activity you want to open.
The navigation library makes this extremely simple and allows you to map URLs directly to destinations in your navigation graph.
<deepLink>
is an element you can add to a destination in your graph. Each <deepLink>
element has a single required attribute: app:uri
.
In addition to a direct URI match, the following features are supported:
www.example.com
will match http://www.example.com
and https://www.example.com
.{placeholder_name}
to match one or more characters. The String value of the placeholder is available in the arguments Bundle which has a key of the same name. For example, http://www.example.com/users/{id}
will match http://www.example.com/users/4
..*
wildcard to match zero or more characters.NavController
will automatically handle ACTION_VIEW intents and look for matching deep links.In this step, you'll add a deep link to www.example.com
.
mobile_navigation.xml
<deepLink>
element to the deeplink_dest
destination.<fragment
android:id="@+id/deeplink_dest"
android:name="com.example.android.codelabs.navigation.DeepLinkFragment"
android:label="@string/deeplink"
tools:layout="@layout/deeplink_fragment">
<argument
android:name="myarg"
android:defaultValue="Android!"/>
<deepLink app:uri="www.example.com/{myarg}" />
</fragment>
AndroidManifest.xml
nav-graph
tag. This will ensure the appropriate intent filter is generated<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<nav-graph android:value="@navigation/mobile_navigation" />
</activity>
adb shell am start -a android.intent.action.VIEW -d "http://www.example.com/urlTest"
www.example.com/urlTest
in the search bar and the disambiguation window will appear.Opened using the search bar (not in Chrome)
Navigation codelab
Disambiguation dialog
Either way, you should see the message "urlTest" on screen. This was passed through to the fragment, from the URL.
There's one more part of the codelab app for you to experiment with, and that's the shopping cart button.
This is a recap of the skills you've learned during this codelab. This step does not include comments, so try it on your own:
NavigationUI
to handle the menu.You're familiar with the basic concepts behind the Navigation component! In this codelab you learned about:
You can continue to explore with this app or start using navigation in your own app.
There's a lot more to try, including:
For more about the Navigation Component check out the documentation.