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:

What you'll build

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):

Jetpack Navigation App

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:

Prerequisites

Get the Code
You should download the project as a Zip file from Blackboard.

Get Android Studio 3.3 or higher

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.

Overview of Navigation

The Navigation Component consists of three key parts, working together in harmony. They are:

  1. Navigation Graph (New XML resource) - This is a resource that contains all navigation-related information in one centralized location. This includes all the places in your app, known as destinations, and possible paths a user could take through your app.
  2. NavHostFragment (Layout XML view) - This is a special widget you add to your layout. It displays different destinations from your Navigation Graph.
  3. NavController (Kotlin/Java object) - This is an object that keeps track of the current position within the navigation graph. It orchestrates swapping destination content in the NavHostFragment as you move through a navigation graph.
    When you navigate, you'll use the 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.

Destinations

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.

Navigation Graph

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:
Nav Graph

Exploring the Navigation Editor

  1. Open res/navigation/mobile_navigation.xml
  2. Click the Design tab at the top right hand corner to go into Design mode. You should see the following:
    Navigation design
    The navigation graph shows the available destinations. The arrows between the destinations are called actions. You'll learn more about actions later.
  3. Click on a destination to see its attributes.
    Destination Step 2
  4. Click on any action, represented by an arrow, to see its attributes.
    Action Selected

Anatomy of a navigation XML file

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:
Nav Graph Code

Notice:

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:

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.

  1. Open res/navigation/mobile_navigation.xml, and click the Design tab.
  2. Click the New Destination icon, and select "settings_fragment"New Destination
    The result is a new destination, which renders a preview of the fragment's layout in the design view.
    Settings Destination
    Note that you can also edit the XML file directly to add destinations. The action you just did above to create a new destination added the following <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.

Activities and Navigation

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.
NavHostFragment
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:

NavController

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:

Navigate to a Destination with 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):

  1. Open HomeFragment.java
  2. Hook up the 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);
		}
	});
  1. Run the app and click the Navigate To Destination button. Note that the button navigates to the 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):
Transitions
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:
Res animations

Add a Custom Transition

Update the code so that pressing the Navigate To Destination button shows a custom transition animation.

  1. Open HomeFragment.java
  2. Use the NavOptions.Builder to create a 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);
    	}
    });
    
    
  3. Remove or comment out the code added in step 5, if it's still there
  4. Verify that tapping the Navigate To Destination button causes the fragment to slide onto the screen and that pressing back causes it to slide off the screen
    Sliding in and out

Actions

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.

Actions

Navigation by actions has the following benefits over navigation by destination:

Notice:

Notice:

Navigate with an Action

Time to hook up the Navigate with Action button so that it lives up to its name!

  1. Open the mobile_navigation.xml file in Design mode
  2. Drag an arrow from home_dest to flow_step_one_dest:
    Nav with action
  3. With the action arrow selected (blue) change the properties of the action so that:
    • ID = next_action
    • Transition for Enter = slide_in_right
    • Transition for Exit = slide_out_left
    • Transitions for Pop Enter = slide_in_left
    • Transitions for Pop Exit = slide_out_right
    Next action attributes
  4. Click the Code tab
    Note the newly added 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>
    
  5. Open HomeFragment.java
  6. Add a click listener to the navigate_action_buttonHomeFragment.java
    Button navActionButton = view.findViewById(R.id.navigate_action_button);
    navActionButton.setOnClickListener(
    	Navigation.createNavigateOnClickListener(R.id.next_action, null)
    );
    
  7. Verify that tapping the Navigate To Action now navigates to the next screen.

Safe Args

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();

Pass a value using safe args

  1. Open the Project build.gradle file and edit it to add the safe args plugin:
    build.gradle
    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
    }
    
  2. Open the 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 {
    	...
    }
    
  3. Open mobile_navigation.xml, and notice how arguments are defined in the flow_step_one_dest destination.
    mobile_navigation.xml
    	<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.
Generated Nav Files
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).

  1. Open FlowStepFragment.java
  2. Comment out the line of code shown below:
    FlowStepFragment.java
    int flowStepNumber = getArguments().getInt("flowStepNumber");
    
    This old-style code is not type-safe. It's better to use safe args.
  3. Update 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();
    

Safe Args Direction classes

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.
Generated Nav Files
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.

Using NavigationUI with an Options menu

One of the easiest ways to use NavigationUI is to have it simplify option menu setup. In particular, NavigationUI simplifies handling the onOptionsItemSelected callback.

  1. Open MainActivity.java
    Notice how you already have the code for inflating the menu overflow_menu in onCreateOptionsMenu
  2. Open res/menu/overflow_menu.xml
  3. Update your overflow menu to include the settings_destoverflow_menu.xml
    <item
    android:id="@+id/settings_dest"
    android:icon="@drawable/ic_settings"
    android:menuCategory="secondary"
    android:title="@string/settings" />
    
  4. Open MainActivity.java
  5. Have 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.
  6. Run your app. You should have a functional ActionBar menu that navigates to the SettingsFragment.
    Settings Menu

Using NavigationUI to configure Bottom Navigation

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.

  1. Open res/layout/navigation_activity/navigation_activity.xml (h470dp) and click the Code tab
    Notice how the XML layout code for bottom navigation is there and refers to bottom_nav_menu.xmlnavigation_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" />
    
  2. Open res/menu/bottom_nav_menu.xml
    Notice how there are two items for the bottom navigation and that their IDs match the destinations of navigation graph destinations:
    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.
  3. Open MainActivity.java
  4. Implement the setupBottomNavMenu method using setupWithNavController(bottomNavigationView: BottomNavigationView, navController: NavController)
    MainActivity.java
    	private void setupBottomNavMenu(NavController navController) {
    		BottomNavigationView bottomNav = findViewById(R.id.bottom_nav_view);
    		NavigationUI.setupWithNavController(bottomNav, navController);
    	}
    
  5. Now run your app and check your bottom navigation works!

Using NavigationUI to configure a Navigation Drawer

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.

  1. Open both navigation_activity.xml and navigation_activity.xml (w960dp)
    Notice how both layouts contain a 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.
    Now to start implementing the NavigationView navigation.
  2. Open MainActivity.java
  3. Implement the 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.
Action Bar Hamburger Menu

  1. Create an AppBarConfiguration by passing in a set of top-level destination IDs and the drawer layout.
    MainActivity.java
    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:

  1. Implement setupActionBarWithNavController
    MainActivity.java
    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.
  2. Override onSupportNavigationUp and call NavigationUI.navigateUp, using the same AppBarConfiguration.
    MainActivity.java
    @Override
    public boolean onSupportNavigateUp() {
    	return NavigationUI.navigateUp(Navigation.findNavController(this, R.id.my_nav_host_fragment), appBarConfiguration)
    			|| super.onSupportNavigateUp();
    }
    
  1. Run your code. If you open the app in split screen, you should have a working navigation drawer. The up icon and the drawer icon should display at the appropriate times and work correctly.

Split Screen

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.

  1. Open menu/nav_drawer_menu.xml
  2. Add a new menu item for settings_destnav_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!
Split Screen Settings

Deep Links and Navigation

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.

Add a Deep Link

We'll use the NavDeepLinkBuilder to hook up an app widget to a destination.

  1. Open DeepLinkAppWidgetProvider.java
  2. Add a PendingIntent constructed with NavDeepLinkBuilder:
    DeepLinkAppWidgetProvider
    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:

  1. Add the Deep Link widget to your home screen. Tap and hold on the home screen to see option to add widget.

Tap and hold

Widget

Scroll down to find widget

Widget

When you are finished you'll have a deep link widget:

Widget

  1. Tap the widget, and verify that the Android destination opens with the correct argument. It should say "From Widget" at the top since that is the argument you passed in DeepLinkAppWidgetProvider.

Widget

  1. Verify that hitting the back button takes you to the home_dest destination.

DeepLink Backstack

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.

The element

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:

Add a URIbased Deep Link using

In this step, you'll add a deep link to www.example.com.

  1. Open mobile_navigation.xml
  2. Add a <deepLink> element to the deeplink_dest destination.
    mobile_navigation.xml
    <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>
    
  3. Open AndroidManifest.xml
  4. Add the nav-graph tag. This will ensure the appropriate intent filter is generated
    AndroidManifest.xml
    <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>
    
  1. Launch your app using a deep link. There are two ways to do this:

Opened using the search bar (not in Chrome)

Search Google

Disambiguation dialog

Select App

Either way, you should see the message "urlTest" on screen. This was passed through to the fragment, from the URL.

See urlTest Message

There's one more part of the codelab app for you to experiment with, and that's the shopping cart button.

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:

  1. Create a new fragment class
  2. Add the fragment as a destination to your navigation graph
  3. Have the shopping cart icon open up your new fragment class, using 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.