Digital Experience

Exploring Android's Navigation Architecture Component


Nish Tahir

Principal Software Engineer

As usual, Google I/O was packed full of announcements. However, this year there was an emphasis on making native Android development easier and more enjoyable. The Navigation Architecture Component, released as part of Android Jetpack and the new AndroidX package, aims to simplify the implementation of navigation in your Android app.

This component and its guidelines offer a new perspective on navigation implementation. They suggest that single-activity architecture be the preferred architecture for Android Jetpack moving forward. The Navigation Architecture Component also supports fragments and deep links out of the box, which will further help create a cleaner, more predictable user experience.

Principles of navigation

Let’s start by taking a brief look at the Principles of Navigation as highlighted by the new guidelines:

1. The app should have a fixed starting destination

The user should always arrive at a fixed destination after launching the app. This excludes exceptions of one-time events such as login or terms and conditions. This lets users know what to expect when opening your app. Your starting destination is expected to be your home or main view. They should contain your primary navigation widgets, such as a bottom navigation bar or navigation drawer.

2. Your navigation should be based on a Last in First Out (LIFO) stack

Your start destination should always be at the bottom of the stack. Navigating to a new destination should logically mean pushing the new destination to the top of the stack, while hitting the back or up button should logically pop the given destination from the top of the stack. All operations that alter navigation or the navigation stack should operate on the top of the stack.

3. The up button should never exit your app

The up button should only be used to navigate within your application and logically pop items off of the navigation stack. If you are at your start destination, this means that your navigation stack should be empty and the up button should not be shown.

4. The up and back buttons should function identically, for the most part

This should be true with the notable exception of situations where the navigation stack is empty. Unlike the up button, which should be hidden, the back button should close the app in these instances.

5. Deep linking to a destination should result in the same stack as normal navigation.

Deep links are an awesome way to take users directly to content that they want to interact with. They should be thought of as potential “quick navigation” methods through the app, not alternative entry points. In order to remain consistent with the principle that your app should have a fixed starting destination, deep linking should result in the same navigation stack that a user would create during normal navigation of your app.

Setup

Getting started with the tools starts as you would expect. You need to add the navigation fragment and UI libraries to your project. They are available via the google() repository.

There is also an optional additional step of including the SafeArgs navigation Gradle plugin. This plugin helps generate code that allows type-safe access to properties used in argument bundles. You will need to add the plugin to your project build.gradle classpath.

In your project build.gradle, you should now be able to apply the Gradle plugin as you normally would.

The Navigation Graph

The navigation graph lies at the core of the new navigation library. It describes how activities and fragments relate to each other and the ways in which you can transition between them.

Here’s an example of what this looks like.

We start with the navigation tag, which contains a single Fragment and Activity. The root navigation element also declares a startDestination tag, which brings us to the concept of destinations. A destination is described as

“… somewhere the user can go”

Programmatically, this would be a fragment or activity declared using its respective XML tags. Destinations have an id property that works as an identifier similar to those used in normal layout files and a name property that is used to reference the fragment or activity to instantiate.

You can create and manage new destinations in your navigation graph using the new Navigation editor available in Android Studio 3.2 Canary 13 and higher.

image-24bb13ea-9b95-4bfe-ab1d-78d83bd13312

It’s worth noting that the start destination is appropriately indicated by the home icon to the left of its ID

Now that we have destinations available on the screen, we can use Actions to navigate between them. Actions describe the intent to navigate between destinations. We can add a simple action that allows us to navigate from the launcher_home fragment to the settings_activity.

We do this using the action tag along with an id and by providing the destination where we intend to navigate.

You can also accomplish this in the navigation editor by dragging an arrow from the launcher_home destination to the settings_activity destination.

image-8540e987-12f4-4dd3-b847-126730e886ae

By selecting the action in the Navigation Editor view, we can note that there are several options available to us to customize in the attributes panel on the right side of the screen. We are able to specify transition animations for properties such as the Enter and Exit transition animations, as well as the Pop Enter and Pop Exit animations. Fortunately, the navigation library comes with default animations for each of these options under the nav_default prefix, such as nav_default_enter_anim. While these are just simple fade animations, they are a welcome convenience.

Navigating

Now that we have a basic graph setup, we can begin incorporating it into our code. We can do this by configuring an activity to act as the Host activity. This activity contains a NavHost, which is an empty view that allows destinations to be swapped in and out as the user navigates through the app. This activity is expected to also contain some form of global navigation, such as a Navigation Drawer or Bottom Navigation Bar.

A default implementation of NavHost is the NavHostFragment widget, which you can add to your layout. We can see an example of this below:

In your host activity, you want to use one of the setup utility methods available in the NavigationUI class. For this example, let’s set up our navigation graph to use a BottomNavigationView.

Your bottom navigation view is populated by an XML menu, which lists out the items as well as the destinations that they correspond to:

Note that the IDs of the menu items should correspond to the IDs of the destinations in the navigation graph. This is how you bind destinations to menu items.

To manually navigate to a destination, you simply need to invoke navigate on your NavController function and pass in an action.

A common use case for this would be navigating in response to a click event is a common use case. The Navigation class provides a convenience utility function to help:

You can also navigate using the generated NavDirections directions classes. For example, to navigate from our MainFragment to our SettingsActivity,

Navigating this way allows you to decouple navigation logic from the rest of your code. Your activities and fragments don’t need to know anything about where the user is coming from or where they are going next. They only need to focus on what they need to do. Decoupling in this way has other benefits. If your routing logic changes, you only need to modify the XML while requiring only minimal changes to your code.

Using safe arguments

The navigation architecture component comes bundled with a way for you to pass arguments through bundles in a type-safe way. It accomplishes this through code generation. Let’s take a look at this in action. Consider a fragment in our Navigation graph,

Here, we add the argument tag, which defines the name of our argument, its type, and a default value if it was not passed.

In our fragment, the value is accessed using the generated Args class for the fragment

And that’s it! The fragment doesn’t need any prior knowledge of where the arguments come from. Let’s explore the generated NotificationFragmentArgs class

The generated class does the wiring that you would typically have to do and exposes a lightweight, typesafe API for you to work with. It also generates a builder that you can use to prepare your own instance.

Unfortunately, there is currently no way to mark fields as optional or not required, and it would have been really nice to be able to create instances of the class using Kotlin’s named parameters instead of the builder, but this is a good first step in the right direction.

Deep linking

Deep linking is a way for you to jump the user to specific parts of your app experience. Following the Principles of Navigation, we should also build the appropriate back stack that users would have created if they had navigated to the item manually. Fortunately, the Navigation Architecture component has got us covered in this area as well.

By adding the following to our XML,

Intent filters, as well as other setup and wiring logic, will be generated for us.

Finally, to add deep links via web URLs, we only need to add the path data to a fragment or activity in our navigation graph.

We can validate this by taking a look at our Merged Manifest.

Note that it not only added the appropriate intent filter actions and categories but also automatically included the scheme with both http and https protocols and properly configured the path.

To ensure we properly support up navigation, we need to override onSupportNavigateUp.

Conclusion

The navigation library is a welcome addition to the architecture components. Android has not historically been able to decouple routing logic from the view layer. Including well-defined actions and type-safe arguments is an excellent step in providing a robust API.

I also strongly advise using the ktx module for navigation for Kotlin projects. The library's extensions functions make for a much nicer API. For example, all of NavigationUI utility methods become extensions of the classes they operate on. There are also convenience extension functions for common tasks such as findNavController on Activity, Fragment, and View.

A great example on using the navigation library is available here.

Be the first to know

Get curated content delivered right to your inbox. No more searching. No more scrolling.

Subscribe now