Home Android & Kotlin Tutorials

Navigation Component for Android Part 2: Graphs and Deep Links

In this tutorial you’ll use the Jetpack Navigation component to write an Android app utilizing graphs and deep links to navigate through different screens.


  • Kotlin 1.3, Android 5.0, Android Studio 3.4

Navigation between Fragments using the FragmentManager is a nightmare most Android developers try to avoid. Usually, the Fragment lifecycle doesn’t play well with the Activity lifecycle.

Managing the Fragment back stack is another headache, especially when you add deep links to the mix. At Google I/O ’18, Jetpack Navigation component came to the rescue. The Navigation component takes the responsibility of the FragmentManager so developers can focus on building awesome features in their apps.

Some of the features in the Navigation component Version 1.0.0 were already covered in The Navigation Architecture Component Tutorial: Getting Started. In this tutorial, you’ll learn about these cool Jetpack Navigation component 2.1.0 features:

  • DialogFragment in navigation graph.
  • ViewModel per navigation graph.
  • Safe Args.
  • Nested graph.
  • Explicit and implicit deep links.
Note: This tutorial also includes Room and Data Binding. Even if you’re not familiar with those topics, you can still follow this tutorial. If you’re totally new to Jetpack, review the Android Jetpack Architecture Components: Getting Started tutorial first.

There’s also an awesome Jetpack Navigation Controller screen cast which quickly walks you through how to build a navigation graph. Be sure to check them out.

To embark on a romantic navigation journey, you’ll build a Love Letter app. As you might guess from the name, this app allows lets you send and receive love letters.

Final App Presentation

Getting Started

Some of the Navigation component features in this tutorial require Android Studio 3.4 or higher. Make sure to install the latest stable release.

First, download the materials for this tutorial using the Download materials button at the top or bottom of this tutorial. Open the project with Android Studio. You’ll be prompted with a dialog to choose Gradle wrapper:

Import Project From Gradle

Click OK. You’ll find the project structure looks like this:

Project Structure

Take a look at the fragments package:

  • InboxFragment: List of letters you received.
  • SentFragment: List of letters you sent.
  • CreateLetterFragment: Where you compose and send your letter.
  • PresentationFragment: Where you view the content of the letter.
  • EditProfileFragment: Dialog for inputting your name and email address.

There are two additional Fragments inside the agreement package:

  • PrivacyPolicyFragment: Long, boring text for the privacy policy nobody cares about.
  • TermsOfServiceFragment: More long, boring text for the terms of service nobody bothers to read.

Here are other important classes:

  • LetterAdapter: RecyclerView adapter for list of letters.
  • LetterPagerAdapter: ViewPager adapter for letter presentation.
  • Event: LiveData value wrapper that makes observer read its value only once.
  • KotlinExtension: Contains a few short-handed functions to make the code cleaner.
  • LetterRepository: Repository class for Letter CRUD operations.
  • LetterViewModel: The only ViewModel in this app for MainActivity and other Fragments.
  • MainActivity: The single Activity where navigation is setup.

To make sure everything is compatible and working, run the app. You should see an empty screen like this:

Empty Screen

The floating action button with the envelope icon is like the Compose button on Gmail app. It’ll bring you to the CreateLetterFragment.

Slide from the left edge to view the navigation drawer. All the drawer menu items are setup for you:

Navigation Drawer

Adding Dependencies

Open build.gradle under app module. Add the following code into dependencies braces below // TUTORIAL DEPENDENCIES:

def nav_version = "2.1.0"

implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

Run Gradle sync through Android Studio. If there aren’t any errors, you’re good to go.

Inflating Layout to Fragment in AndroidX

Because you added androidx.navigation:navigation-fragment-ktx, you’re now able to set the layout for the Fragment directly in its constructor. Open the following five files one at a time, and add the layout id to each constructor:

class InboxFragment : Fragment(R.layout.fragment_inbox)

class SentFragment : Fragment(R.layout.fragment_sent)

class PresentationFragment : Fragment(R.layout.fragment_presentation)

class PrivacyPolicyFragment : Fragment(R.layout.fragment_privacy_policy)

class TermsOfServiceFragment : Fragment(R.layout.fragment_terms_of_service)

The benefit of this approach and setting the layout in the Fragment constructor is that you don’t have to override onCreateView() and write boilerplate code to inflate the layout and return it.

Building Graphs With the Navigation Component

Right click the res folder in Project Explorer, then create a new Directory named navigation. Right click the navigation folder and create a new Navigation resource file named nav_graph. You’ll be brought to the Navigation Editor:

Navigation Editor

Add the following five fragments and the agreement graph to the editor. Connect them as in the screenshot below:

  • InboxFragment, set as home fragment.
  • SentFragment.
  • CreateLetterFragment.
  • PresentationFragment.
  • EditProfileFragment, shown as a dialog.

Navigation Graph

You only need to connect the action from InboxFragment and SentFragment to PresentationFragment. You’ll find out why in the Safe Args section below.

Switch the view mode from Design to Text. Fill in navigation id, action id and fragment label. Then, fill in layout as the code below, or simply copy and paste:


Note: The <dialog> tag is used instead of <fragment> for EditProfileFragment, otherwise the fragment would take up the full space on the screen.

If the fragments in Navigation Editor don’t render any layout, make sure that the tools:layout attributes are added. You could also simply rebuild the project.

You’ve finished building the navigation graphs. Now you’re ready to use nav_graph in content_main.xml.

Adding NavHostFragment With Navigation Component Graph

Open content_main.xml and add the following code inside the ConstraintLayout:


What you should notice here:

  • android:name="androidx.navigation.fragment.NavHostFragment": You’re using NavHostFragment from Androidx to host your navigation graph.
  • app:navGraph="@navigation/nav_graph": You’re supplying your navigation graph to the NavHostFragment.

Now it’s time to let your MainActivity know about the NavHostFragment and navigation graph.

Setting up Navigation Component in MainActivity

Open MainActivity.kt and examine the code. You’ll see some code for navigation drawer and data binding is already there. Data binding binds the profile name and email to the navigation drawer header.

There are a bunch of TODOs to help you know where to insert the code snippets.

Right below the MainActivity class declaration, add the following two private fields:

private val navController by lazy { findNavController(R.id.nav_host_fragment) } //1
private val appBarConfiguration by lazy {
    ), drawerLayout
} //2

Here’s what you did:

  1. You’ll use navController to navigate from one fragment to another. Import findNavController from androidx.navigation.findNavController.
  2. appBarConfiguration defines which fragments are the top level fragments so the drawerLayout and hamburger icon can work properly. You’ll understand why when you run the app.

Connecting Toolbar, Drawer and Navigation Controller

Here’s the most important code in this tutorial. While you’re still in MainActivity, add the following code inside setupNavigation:

NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)  //1

NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)  //2

navController.addOnDestinationChangedListener { _, destination, _ ->  //3
  if (destination.id in arrayOf(
  ) {
  } else {

  if (destination.id == R.id.presentationFragment) {
    toolbar.visibility = View.GONE
  } else {
    toolbar.visibility = View.VISIBLE

Here’s what this code does:

  1. Connect your navController with drawerLayout.
  2. Connect your toolbar, navController and appBarConfiguration together. This makes your navController aware of top-level fragments defined in appBarConfiguration.
  3. Customize your floating action button and toolbar based on the destination fragment. Here you want to hide your floating action button when you’re on CreateLetterFragment, PresentationFragment and EditProfileFragment. On PresentationFragment, hide the toolbar so that you can read your love letters without distraction.

Now, run the app. You should now see the hamburger icon. It wasn’t there when you first ran the app. That’s because of the second line of the code above.

The title at tool bar is shows the label of InboxFragment, which is the current Fragment.

Inbox Screen

Click the hamburger icon or slide from the left side of the screen. You’ll see the drawer is open. But if you click on any menu item, you still can’t navigate to any fragment yet.

Navigating From Drawer Menu Items

Inside onNavigationItemSelected, you’ll find the when block for each menu item. Replace the
content inside the when block with the following code:

R.id.nav_inbox -> {
  navController.popBackStack(R.id.inboxFragment, false)  //1

R.id.nav_sent -> {
  navController.navigate(R.id.sentFragment)  //2

R.id.nav_privacy_policy -> {
  navController.navigate(Uri.parse("loveletter://agreement/privacy-policy"))  //3

R.id.nav_terms_of_service -> {
  navController.navigate(Uri.parse("loveletter://agreement/terms-of-service"))  //4

Here’s what you added:

  1. Since InboxFragment is the home fragment, you use popBackStack to remove other fragments from the navigation stack. The second parameter indicates whether InboxFragment should also pop out of the stack.
  2. Straight forward. This uses the navController to navigate to SentFragment.
  3. This is a deep link URI to PrivacyPolicyFragment. You’ll learn about this in a few sections.
  4. This is also a deep link URI, but for TermsOfServiceFragment.

Now, run the app. You should be able to navigate between InboxFragment and SentFragment.

That’s it. You no longer have to get SupportFragmentManger or commit a fragment transaction in order to swap fragments.

Open Sent

Note: Currently, if you click on Privacy Policy or Terms of Service the app will crash. This is because no app is handling the URI you defined in the code snippet above.

Navigating From Floating Action Button

In MainActivity.kt, locate setupViews() and add the following code inside


Similar to how you navigate to SentFragment, you use navController to navigate to
CreateLetterFragment by its id.

Run the app and click the floating action button. You’ll see the app navigates to CreateLetterFragment.

The loading progress bar keeps on spinning. It’s an annoyance, but you can ignore it for now.

Create Letter Screen

Next, you’ll wire the edit profile dialog.

Showing DialogFragment

If you’ve tried showing DialogFragment in Navigation component 1.0.0 or followed The Navigation Architecture Component Tutorial: Getting Started tutorial, you have extended Navigator class and implemented your own show dialog logic.

This isn’t required with Navigation component 2.1.0. Take a look at the code in nav_graph.xml. You’ll see the tag dialog is automatically used for fragments extending DialogFragment.

To show the dialog fragment, you have to add the following line inside the click listener of headerBinding.ivEdit in MainActivity:


Once again, we’re using the navController to navigate to another fragment. In this case the Edit Profile Fragment. Run the app and click the Edit Profile button.

Yay! The dialog pops up with only a few lines of code.

Open Edit Profile Dialog

Next, you’ll set the ViewModel so you can save and show your profile information.

ViewModel Across Navigation Graph

ViewModel is a design pattern introduced to separate the business logic and life cycle events from your Activity and Fragment. Since your fragments live under an Activity, it’s common to use one shared ViewModel for the Activity and its associated fragments.

In the Navigation component, you can initialize a ViewModel with a navigation graph scope. This means all the fragments in the same navigation graph and their parent Activity share the same ViewModel.

Note: If you’ve never used ViewModel or data binding, check out the MVVM and DataBinding: Android Design Patterns and other related tutorials.

Getting ViewModel in Activity From Navigation Graph

While you’re still in MainActivity.kt, add the following code to setupViewModel():

try {
  val viewModelProvider = ViewModelProvider(
  )  //1
  lettersViewModel = viewModelProvider.get(LettersViewModel::class.java)  //2
  headerBinding.viewModel = lettersViewModel  //3
  lettersViewModel?.loadProfile()  //4
} catch (e: IllegalArgumentException) {  //5

Here’s a breakdown of the above code:

  1. Instead of the usual way of getting ViewModelProviders from Activity or Fragment like ViewModelProviders.of(this), here you get ViewModelProvider from navigation graph id, which is R.id.nav_graph. This ensures you’re using the same ViewModel across entire graph.
  2. Get your LettersViewModel from viewModelProvider, which you created in the line above.
  3. This is the data binding logic for the header in the navigation drawer. If you’re curious, check nav_header_main.xml. The header binding requires the viewModel instance of LettersViewModel class.
  4. Load profile information from SharedPreferences.
  5. An exception happens when the app is launched via a deep link. Navigation component can’t handle the initialization of a ViewModel scoped to Navigation graph. The temporary solution is to catch the exception.

The details of ViewModel implementation aren’t discussed here since it’s not part of the Navigation component. Spend some times reading the code in LettersViewModel to understand how the ViewModel, Data Binding and Room operate behind the scenes.

Now your drawer header is ready to show profile information from shared preferences. But nothing is saved to shared preferences yet! You need to do something in the EditProfileFragment.

Getting ViewModel in Fragment From Navigation Graph

Open EditProfileFragment.kt and replace the declaration of lettersViewModel with the following line:

private val lettersViewModel: LettersViewModel by navGraphViewModels(R.id.nav_graph)

This is much cleaner than how you got your ViewModel in MainActivity earlier. Thanks to navGraphViewModels(...), the extended inline function for Fragment, you can get the instance of LettersViewModel with one line of code.

Launch the app and edit your profile information in the dialog. Click the Save button and check your profile in the drawer again.

Voila! Your name and email are there! If you open the EditProfileFragment dialog again, your name and email are filled by default.

That’s the magic of data binding. :]

Fill In Edit Profile Dialog

Creating Your First Love Letter

Open CreateLetterFragment.kt and replace the declaration of lettersViewModel with the following code:

private val lettersViewModel: LettersViewModel by navGraphViewModels(R.id.nav_graph)

Once again, it’s the same line to get the instance of the same ViewModel.

In the Create Letter Fragment, you might have noticed there are two option menu items:

  • PUSH: Create a local push notification. This mimics the notification the app sends when your recipient receives your letter. In this tutorial, you’ll only send love letters to yourself.
  • SEND: Create a web URI for a deep link. The URI is supposed to be embedded in the love letter email. If the recipient also has the Love Letter app installed on his or her phone, when he or she clicks the URI in the email app, the phone should launch the Love Letter app to handle the deep link.

When you click SEND or PUSH, handleSend gets called. Take a look at handleSend.

private fun handleSend(toSend: () -> Unit) {
  if (lettersViewModel != null && lettersViewModel.hasFullProfile()) {
    // TODO: navigate to sent fragment

  } else {
    // TODO: navigate to edit profile fragment


handleSend is checking if there is any profile information. It’s making sure the name and email aren’t empty.

If there is profile information, invoke toSend lambda and navigate to SentFragment. Otherwise, show EditProfileFragment dialog.

Based on this logic, replace the implementation of handleSend with the following code:

if (lettersViewModel.hasFullProfile()) {
  findNavController().popBackStack(R.id.inboxFragment, false)  //1
  findNavController().navigate(R.id.sentFragment)  //2
} else {
  findNavController().navigate(R.id.editProfileFragment)  //3

Here’s a breakdown of the code above:

  1. In a fragment, call findNavController() to get the navigation controller. After you’ve done your sent action, exit CreateLetterFragment and go to SentFragment to see what you’ve sent. You can’t popBackStack directly to SentFragment because it might not be in the navigation stack.

    The work around here is to go back to InboxFragment, which is the home fragment, first.

  2. Here you go to SentFragment from InboxFragment.
  3. If the app doesn’t have profile information, go to EditProfileFragment.
Note: There are a few variants of findNavController(). Make sure to import the one under androidx.navigation.fragment.

Before you can test the PUSH function, you need to learn about how to generate a push notification with deep link.

Building Notification With Explicit Deep Link

There are two types of deep links:

  • Explicit: An explicit deep link is a single instance of a deep link that uses a PendingIntent to take users to a specific location within your app.
  • Implicit: An implicit deep link is a URI that refers to a specific destination in an app.

Open NotificationHelper.kt. Examine the code and you’ll find three methods:

  1. sendLocalNotification: Gets notificationManager from context and sends the notification.
  2. sendLocalNotification: Builds a notification from the letter object.
  3. buildPendingIntentFromNavigation: Builds a pending intent to navigate to specific fragment when notification banner is clicked.

There’s no implementation inside buildPendingIntentFromNavigation yet. Replace the code inside buildPendingIntentFromNavigation with the code below:

val bundle = Bundle()
bundle.putString(EXTRA_LETTER, gson.toJson(letter))  //1
return NavDeepLinkBuilder(context)  //2
  .setGraph(R.navigation.nav_graph)  //3
  .setDestination(R.id.presentationFragment)  //4
  .setArguments(bundle)  //4
  .createPendingIntent()  //5

Here you:

  1. Create the bundle which contains the serialized letter string.
  2. Use NavDeepLinkBuilder to build a pending intent.
  3. Pass in your navigation graph resource id.
  4. Pass in your fragment ID in navigation graph.
  5. Generate the pending intent. Done!

Run the app and send your first love letter. Go to the create letter screen from the floating action button. Write a message to your loved one and click the PUSH button.

You should see a push notification on the top of your screen. Clicking the notification won’t work because you haven’t configured the PresentationFragment.

Push Notification

Create another letter and click the SEND button. Observe the Logcat in Android Studio. You’ll see a link like this:

Logcat of Sent Message


You’ll use this link for the implicit deep link implementation in a later section.

What if you haven’t filled in your profile information? If you already have profile information, click the edit profile button from the drawer header, clear the name and email field and click SAVE.

Now go back to create letter screen, click either the PUSH or SEND button. You’ll see the edit profile dialog! This is really cool because you can reuse the dialog with one line of navigation code.

Passing Letter With Safe Args

Safe Args is a Gradle plugin that generates objects and builder classes for type-safe access to arguments specified for destinations and actions. In other words, it forces you to pass correct and sufficient arguments into the fragment when you navigate to it.

To use Safe Args, open build.gradle under app. Add the following line of code the top of the file:

apply plugin: "androidx.navigation.safeargs.kotlin"

Run Gradle sync in Android Studio again.

Now, you’ll make PresentationFragment require a serialized Letter string as an argument. PresentationFragment converts the serialized Letter string into an object and assigns it to the ViewPager.

Open nav_graph.xml, on Design view, then click the presentationFragment in the graph on the left. On the Attributes panel, you’ll find the Arguments section. Click the + on the right hand side.

Attributes Panel

Name the argument letter and set its type as String. Click Add. That’s it.

Add Letter Argument

Getting Letter From Safe Args

Open PresentationFragment.kt. Assign lettersViewModel to the navigation graph ViewModel:

private val lettersViewModel: LettersViewModel by navGraphViewModels(R.id.nav_graph)

Then, add the following line of code right below the line above to get the argument’s object:

private val args: PresentationFragmentArgs by navArgs()
Note: PresentationFragmentArgs is a generated class. If the compiler gives you an error saying that it can’t find the class, make sure to build your project in Android Studio first.

Inside onViewCreated, under super.onViewCreated(view, savedInstanceState), add the following code:

val letter = Gson().fromJson(
  args.letter.urlDecode(),  //1
lettersViewModel.saveLetterToInbox(letter)  //2

viewPager.adapter = context?.let {
    it, letter  //3

Here’s a breakdown of this code:

  1. The args object contains the letter argument. It’s type is String. You can go to its declaration to see the code of the generated class. urlDecode() is a String extension that decodes the URL-encoded string because the serialized Letter is used as a part of URL.
  2. Save the opened letter to inbox. This mimics the behavior of receiving the letters in the inbox so you can reopen them.
  3. Pass the Letter object to the ViewPager. The ViewPager presents the letter in three sliding pages.

You’ve setup the PresentationFragment. Now you can PUSH a new letter and open it from a push notification!

Open From Push Notification

Give yourself a pat on the back! :]

Seeing What You’ve Sent

Open SentFragment.kt. To see the letters you’ve sent, you need to assign the same nav-graph-scoped ViewModel to lettersViewModel. Assign the value to lettersViewModel as follows:

private val lettersViewModel: LettersViewModel by navGraphViewModels(R.id.nav_graph)

No surprise here. It’s the same old code.

Next, handle the click on a RecyclerView item. Add the following code to adapter.setItemClickListener:


So far, you’ve used navigation by fragment id. In the line of code above, you use the generated SentFragmentDirections class.

Navigation component generates a class for each fragment which has an action to another fragment. The name of the generated class is the name of the originating destination, appended with the word Directions.

presentLetter is the id you gave the action from SentFragment to PresentationFragment. Here, you use it to pass the safe args.

Finish the next step before running your app. It’ll be a quick one.

Seeing What You’ve Received

Repeat the same steps from SentFragment in InboxFragment. In InboxFragment.kt, Assign the value to lettersViewModel as follows:

private val lettersViewModel: LettersViewModel by navGraphViewModels(R.id.nav_graph)

Add the following code to adapter.setItemClickListener:


Now you can test the whole flow of sending and receiving a love letter. You should see letters in the SentFragment and InboxFragment. Clicking any letter in the list should open the PresentationFragment.

Inbox To Sent Screens

Nested Graph

If you’re building a large-scale app, you should create different graphs for modularization and reusability. Similar to layout files, you can nest a new graph in the current graph.

You’ll create another graph for Privacy Policy and Terms of Service. Create another navigation graph in the same location as nav_graph.xml. Name it agreement_graph.xml.

  1. Add PrivacyPolicyFragment and TermsOfServiceFragment to Navigation Editor.
  2. Set PrivacyPolicyFragment as the home fragment.
  3. Connect PrivacyPolicyFragment to TermsOfServiceFragment.
  4. Switch to Text view and change their label to proper name from String resource.
  5. Set the id of the navigation graph to android:id="@+id/agreement_graph".


Agreements Graph

Next, switch to nav_graph.xml. Add the nested agreement_graph the same way you added the other fragments in Design view. That’s by clicking the new destination button, you don’t have to connect any fragment to the nested graph.

Now, how do you navigate to the nested graph?

Nested Agreements Graph

Creating Implicit Deep Link With URI

The answer is simple: Use a deep link. More precisely, use an implicit deep link. Implicit deep links allow you to navigate to any fragment by URI.

Go back to agreement_graph.xml. In Design view, with PrivacyPolicyFragment selected, find the Deep Links section on the Attributes panel.

Deep Link Attribute

Click the + sign, and add the following deep link in the URI textfield:


Add Privacy Policy Deep Link

Repeat the steps above to add another deep link for TermsOfServiceFragment:


Add Terms of Service Deep Link

Do you remember the Privacy Policy and Terms of Service drawer menu items at the beginning of the tutorial? Now that everything is connected, you can test them out.

Open Agreement Screens

Handling Web URL Deep Link

Assume you own http://www.loveletter.com. When someone sends a love letter, you want to embed a link in the email.

If the recipient clicks the link on his phone app client, and his phone has Love Letter app installed, the phone should prompt the recipient to launch the app to see the letter. Otherwise, the recipient should be able to open the letter on any web browser.

Open AndroidManifest.xml. Add the following line inside <activity> and above <intent-filter>:

<nav-graph android:value="@navigation/nav_graph" />

This is a crucial step that’s easy to forget. If you have another deep link to another navigation graph, remember to add the graph here.

Next, go to nav_graph.xml. Add the following deep link to PresentationFragment:


Don’t include http:// or https:// for web URL. {letter} is the encoded-URL which should passed to PresentationFragment.

Does the URL looks familiar? You saw it in the Logcat when you clicked the SEND button on CreateLetterFragment. Here, you configure it to handle that link.

With all the puzzle pieces complete, you can test the deep link like this:

  1. Go to Run ▶ Edit Configurations.
  2. Add new a Android App configuration.
  3. Name it letter-deeplink.
  4. Select Module: app.
  5. Select Launch: URL.
  6. Paste in the URL you got from Logcat after you clicked the SEND button.
  7. Click OK to finish.

Run Configurations

Change the run configuration from app to letter-deeplink and run it.

Launch App With Deep Link

Where to Go From Here?

Congrats! You should now be able to navigate to any fragment in any way by controlling the actions and destinations with the Navigation component.

You can download the finished project with the Download materials button at the top or bottom of the tutorial.

Jetpack Navigation component also works with Bottom Navigation. Try adding Bottom Navigation and connecting it to the navigation controller with NavigationUI.setupWithNavController(...).

You can find additional examples of Navigation at the official Android Developer website. You can also find a list of guidelines for Navigation on Android Developers blog.

If you have any questions or comments on what you’ve learned, join the forum discussion below!




More like this