Navigation Architecture Component for the Rest of Us

Updated for navigation component v2.2.1 on April 5th, 2020
Recently I had a chance to try out the new Navigation Architecture Component library and wanted to share what I learned. I will walk you through applying the new navigation architecture to a Notes/todo app. Using a basic Note-taking app is an effective way to learn a new library or framework without requiring knowledge of any business domain.
The app is in Kotlin and follows the popular MVVM architecture with help of other Jetpack libraries such as ViewModel and LiveData.
Complete source code for this article is available on Github. Feel free to browse the source code directly or scan the pull requests (there is one for each feature).
Here are some things we’ll be looking at:
- General project and
NavHostFragment
setup - Nav Drawer setup
- Setting up domain layer
- Implementing note list, detail, edit and delete functionality
- Using the navigation drawer
- Setting up deep links
- Adding transition animations
- Managing top-level destinations with AppBarConfiguration
- Bonus: migrating from support library to AndroidX
General project and NavHostFragment
setup
To use the new navigation editor, you will need to use Android Studio Canary. Some of the New Project templates already include basic Navigation included but I’d recommend starting from scratch — it’s the best way to learn.
Create a new navigation resource folder and an empty navigation graph XML file.

Keeping your navigation graph in xml makes it easy to diff navigation changes over time, i.e. enables better code reviews.
We will be passing arguments between the screens so we need to add this Gradle plugin to the top-level build.gradle (complete build.gradle can be found here).
In order to pass arguments between your Fragments (assuming you are building a single Activity app), you will also need to add the safeargs
plugin to app/build.gradle:
We will use Kotlin library versions to take advantage of the extension functions. Add these dependencies to app/build.gradle. We’ll need other dependencies as well to complete various aspects of the app so check out the complete app/build.gradle here.
Google recommends a single Activity with multiple Fragments when using the new Navigation component. Navigating via Fragments simplifies the lifecycle greatly as you don’t have to deal with complexity of interaction between Activity and Fragment lifecycles. It also makes applying shared element transitions and transition animations very easy, as we’ll see below. You can use Activities as your destinations but for the reasons above, Google’s recommendation is to use Fragments. Activities as destinations make sense when your navigation flow involves interaction with other apps.
One of the biggest benefits of working with the new Navigation Arch Component is that we can edit and see our navigation graph visually in the Design View. When we are done with this tutorial, we will have the following flow:

The arrows represent actions. It is easy to see that from the notesFragment (start destination) we can go to either noteDetailFragment or addNoteFragment. Once on noteDetailFragment, we can go to either editNoteFragment or deleteNoteFragment.
Let’s create our activity_main.xml layout first:
This layout has a Nav Drawer which will be automatically synchronized with our navigation graph (choosing a menu item in the nav drawer will take us to that navigation destination). When the backstack is empty, the Up indicator will be replaced by the hamburger menu icon. There is a nice built-in animation to toggle between them: no need to integrate the ActionBarDrawerToggle
yourself.
The NavHostFragment
is the fragment container outlined below by the red box which is used as a container for different fragments within the navigation flow.

Add our one and only Activity in this app:
NavController
At this point we have a NavController
set up which will work with the currently running NavHostFragment
to enable navigation actions including deep linking, maintaining backstack, managing action bar and the nav drawer icon.
NavController
manages all things navigation for us while we never have to touch FragmentManager
or FragmentTransaction
directly!
Let’s look at each function in the MainActivity
separately:
- setupActionBarWithNavController ensures that the title in the action bar will automatically be updated when the destination changes (assuming that
android:label
values are set up). In addition, the Up button will be displayed when you are on a non-root destination and the hamburger icon will be displayed when on the root destination. - setupWithNavController ensures that the selected item in the
NavigationView
will automatically be updated when the destination changes. - navigateUp ensures that the menu items in the Nav Drawer stay in sync with the navigation graph.
These functions belong to androidx.navigation.ui.NavigationUI
class found in the navigation-ui dependency.
NavigationUI provides support the following top app bar types:
For now our navigation drawer is empty but we’ll populate it soon.
Set up domain layer
Let’s throw together a basic set of domain classes that the rest of the app can use. Our domain consists of only Note
model class and NotesManager
singleton object which allows reading, updating and deleting notes.
Implement Note List screen
Update the mobile_navigation.xml to include the new Fragment:
NoteListFragment
will be the default Fragment destination in our app (it will be the first screen shown when the app loads) as set in the app:startDestination
attribute. Note that the android:label
will be visible in the ActionBar.
Actions aka Routes
The NoteListFragment
destination defines two available actions
. Hopefully, you will find my naming conventions self-documenting.
- action_notes_to_addNote will take the user to
AddNoteFragment
. This action will be used when the user clicks on the FAB. - action_notes_to_noteDetail will take the user to
NoteDetailFragment
. This action will be used when the user clicks on an item in theRecyclerView
.
There are a few things about the actions that you can control right in the navigation graph xml:

Add the ViewModel
which will interact with NotesManager
to load notes.
Finally, add the NoteListFragment
which will observe observableNoteList
from our ViewModel
As you may recall from above, there are 2 possible navigation actions that can occur in this Fragment and here is how we use them.
fab.setOnClickListener {
val action = NoteListFragmentDirections
.actionNotesToAddNote()
findNavController(it).navigate(action)
}
and
private fun onNoteClicked(note: Note) {
val action = NoteListFragmentDirections
.actionNotesToNoteDetail(note.id)
findNavController().navigate(action)
}
Note that the action names are converted into camelCase for you so they conform to the standard Java naming conventions.
Also note that the actionNotesToNoteDetail
takes a mandatory note.id
param which will be set up below. If we fail to include the param, we’ll get a compilation error.
Implement Note Detail screen
Update the mobile_navigation.xml to include the new Fragment:
Here we define that NoteDetailFragment
expects a mandatory noteId
param of type integer
. After all it would not make sense to start this Fragment without the noteId
argument — we would not know what to load!
If this parameter was optional, we’d use the defaultValue
attribute like so:
<argument
android:name="noteId"
app:argType="integer"
android:defaultValue="0" />
The following actions are logical navigation choices available from the NoteDetailFragment
:
- action_noteDetail_to_editNote
- action_noteDetail_to_deleteNote
Here is the actual NoteDetailFragment
code:
Note that the parameter noteId is retrieved by this Fragment using
private val args by navArgs<NoteDetailFragmentArgs>()
which is a static auto-generated and imported method on the NoteDetailFragmentArgs
.
If you look at the build folder, you can see all args-related code generated for you by the safe-args-gradle-plugin. This plug-in gives us compile-time checks for parameters presence and correct types used.

Here is how navigating to the Edit Note screen is accomplished (again, the noteId
argument is essential for the EditNoteFragment
)
noteId
argument is extracted using by navArgs
as well:
private val args by navArgs<EditNoteFragmentArgs>()
and then passed in as a param when creating actionNoteDetailToEditNote:
editNoteButton.setOnClickListener {
val action = NoteDetailFragmentDirections
.actionNoteDetailToEditNote(args.noteId)
findNavController(it).navigate(action)
}
Similarly, the DeleteNoteFragment
needs the noteId
to know what to delete.
deleteNoteButton.setOnClickListener {
val action = NoteDetailFragmentDirections
.actionNoteDetailToDeleteNote(args.noteId) findNavController(it).navigate(action)
}
This is the power of SafeArgs Gradle plugin for the Navigation Component. Lyla Fujiwara described it perfectly:
Generates classes based off of your navigation graph to ensure type-safe access to arguments for destinations and actions.
Implement Delete Note screen
We’ll skip the AddNoteFragment
and EditNoteFragment
(you can check them out on Github) and move on to the DeleteNoteFragment
which introduces new ways to navigate in addition to what we’ve covered above. It is implemented as a regular Fragment
but in ideal world the Confirm Delete functionality should be implemented as a DialogFragment
. Unfortunately, this is currently not supported by the library.
Update the mobile_navigation.xml to include the new Fragment:
As you see, this Fragment also needs a noteId
argument to determine which note to delete.
Here is the DeleteNoteFragment
itself:
When the Cancel Button is clicked, we pop back stack (navigate back up the navigation hierarchy back to the Note Detail screen)
cancelDeleteButton.setOnClickListener {
findNavController(it).popBackStack()
}
When the note is successfully deleted, it does not make sense to pop back stack (to navigate back up to the Note Detail screen) since the note is gone from our data source by then. Instead, we navigate all the way back to the starting screen of the app (Note List screen) by popping back stack to that particular destination.
confirmDeleteButton.setOnClickListener {
viewModel.deleteNote(args.noteId)
}
Add navigation drawer menu item
Let’s create an empty SearchNotesFragment
and add it to the mobile_navigation.xml and the nav drawer.
Now the menu_nav_drawer.xml can be edited to include the new destination:
And that’s it! The work we did earlier in the MainActivity
to link the NavController
to the Nav Drawer NavigationView
will guarantee that the navigation drawer always stays in sync with the current navigation selection.
Set up deep links
There are two types of deep links:
- Explicit (NavDeepLinkBuilder can be used to programmatically set up a deep link)
- Implicit (deep links are configured declaratively in our mobile_navigation.xml)
Let’s take a look at an implicit deep link. Assume that we need to deep link directly into a Note Detail screen to a given note by a noteId
. We can simply add the following tag within the fragment definition for NoteDetailFragment
:
<deepLink
android:id="@+id/noteDetailDeepLink"
app:uri="notesapp://notes/{noteId}" />
As long as the name in curly brackets ({noteId
}) matches the name of the argument we defined earlier for this Fragment, things should just work! Then in AndroidManifest.xml, include the nav-graph
tag and point to our navigation graph:
<activity android:name=".presentation.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<nav-graph android:value="@navigation/mobile_navigation" />
</activity>
No need to set up intent filters manually! That will all be done for you by the Navigation library by way of AndroidManifest merge.
Testing deep links
To test my deep link, I executed the following adb
command after creating several notes in the app:
adb shell am start -a android.intent.action.VIEW -d "notesapp://notes/2" com.jshvarts.notesnavigation
I was successfully deep linked directly into the Note Detail screen with note 2 loaded.
Besides being super easy to set up deep links, a huge benefit you get is consistent navigation. There is no need to manually synthesize the backstack.
Clicking the Up button or hitting the Back button after deep linking, takes you to the Note List screen because the deep link is set up within the navigation graph which is aware that the NoteListFragment
is a parent of the NoteDetailFragment
. No need to manually code the Up and Back button behaviors anymore.
Add transition animations
Let’s add a couple of animations to the NoteListFragment
. We can do declaratively in the navigation graph xml.
The animations apply to an action
. In this case, we will animate the transition from the Notes screen to a Note Detail screen. To do so, you can modify the navigation graph file manually or use the excellent navigation editor in Android Studio.
- enterAnim specifies how the Note Detail screen will be animated when it is navigated to.
- exitAnim specifies how the Notes screen will be animated when it is navigated from.
- popEnterAnim specifies how the Notes screen will be animated when it is navigated back to (when backstack is popped on the Note Detail screen by using the Up or Back buttons).
- popExitAnim specifies how the Note Detail screen will be animated when it is navigated from (when backstack is popped on the Note Detail screen by using the Up or Back buttons)
The navigation-ui dependency comes with a couple of basic built-in animations and, as you see, it is a breeze to add new ones. It’s almost too easy!
The Navigation AAC also simplifies shared element transitions between destinations. Everyone who has dealt with shared element transitions before would know what a pain it is. Including this functionality in the navigation library is a welcome move! This feature can be implemented programmatically only since you need to reference specific views.
XML vs Programmatic Approach
Setting up the navigation graph in XML is only one way to use the new Navigation Arch Component. You can do all the same things programmatically in code. However, I see a huge benefit in being able to model your navigation flows in XML (with or without nested graphs). It makes it easier to see and edit (in or outside of the visual editor) all of the navigation rules including arguments and deep links in one place. It serves as documentation for your project and makes maintaining this code easier in the future. Unlike Storyboard on iOS, the navigation graph can be easily diffed and code reviewed just like a regular layout XML file.
Managing top-level destinations with AppBarConfiguration
You can use AppBarConfiguration to customize your top-level destinations (ones that don’t have an Up button in the top app bar).
To customize which destinations are considered top-level, pass a set of destination IDs to the AppBarConfiguration
constructor:
val appBarConfiguration = AppBarConfiguration(setOf(R.id.main, R.id.android))
Or pass a DrawerLayout
to the AppBarConfiguration
constructor so that the drawer icon is displayed on all top level destinations:
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
Bonus: Migrating to AndroidX
Originally this app was targeting api level 27 and used the support library.
Check out this commit for migrating steps to AndroidX. And don’t forget to set the compile SDK version to 29 (Android Q) in the app module settings in the Android Studio.
For a small app like this one, the migration to AndroidX was a piece of Pie which is very encouraging.
Conclusion
I really like the new Navigation Architecture Component. While still in alpha, the library is already mature enough to start using in Production. Google is doing a great job seeking ideas and bug reports from developers and helping out on StackOverflow (just use the right tags: android-architecture-navigation, android-navigation-component). Kudos to Ian Lake in particular.
Having a Single Source of Truth navigation graph to drive the entire app including the deep links is a huge step forward in making development easier and user experience more consistent.
Personally, I would love to be able to test the navigation graph (or nested navigation graphs) with JUnit alone without resorting to Espresso and hope that more testing abilities are added in the near future.
Some complain that the library does not give you enough control over the backstack but I am yet to run into this issue myself. I like keeping things simple and intuitive — it speeds up both development and maintenance.
I believe that the new library is well on its way to becoming the navigation library on Android.
Resources
Visit my Android blog to read about Jetpack Compose and other Android topics