Using Jetpack Navigation Component in Android

In this blog, we are going to learn how to integrate the navigation component, how to see the complete navigation graph of our application, and how to pass arguments safely.

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:

  • Automatic handling of fragment transactions
  • Correctly handling up and back actions by default
  • Default behaviors for animations and transitions
  • Deep linking as a first-class operation
  • Implementing navigation UI patterns (like navigation drawers and bottom nav) with little additional work
  • Type safety when passing information while navigating
  • Android Studio tooling for visualizing and editing the navigation flow of an app
The Navigation component requires Android Studio 3.3 or higher and is dependent on Java 8 language features.

Overview

The Navigation Component consists of three key parts:

  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.

Integration

Just include the following code in the dependencies block of your module-level build.gradle file

  def nav_version = "2.2.2"
  // Java language implementation
  implementation "androidx.navigation:navigation-fragment:$nav_version"
  implementation "androidx.navigation:navigation-ui:$nav_version"

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

Navigation Graph

First, we will create a file that will contain our navigation graph. In the res, directory create a new android resource file as follows

This will create an empty resource file named nav_graph.xml under the navigation directory.

For the sake of demonstration, I have created a sample app that contains two fragments named FirstFragment and SecondFragment. FirstFragment has a button on click of which we will navigate to the SecondFragment.

We define these fragments in the navigation graph as below

<navigation 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:id="@+id/nav_graph"
    app:startDestination="@id/nav_first_fragment">

    <fragment
        android:id="@+id/nav_first_fragment"
        android:name="app.navigationcomponentexample.FirstFragment"
        tools:layout="@layout/fragment_first">

        <action
            android:id="@+id/action_first_to_second"
            app:destination="@id/nav_second_fragment"/>

    </fragment>

    <fragment
        android:id="@+id/nav_second_fragment"
        android:name="app.navigationcomponentexample.SecondFragment"
        tools:layout="@layout/fragment_second"/>

</navigation>

Here the root tag named navigation has a parameter called app:startDestination which has the id of our first fragment. This defines that the first fragment will be loaded in the NavHostFragment automatically

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.

Notice that for first fragment we have defined an action with the following attributes:

android:id="@+id/nav_first_fragment"
app:destination="@id/nav_second_fragment"

Each action should have a unique id which we will use to navigate to the required destination.

Here the destination points to the id of the second fragment defined in the nav graph, which means that with this action we will navigate to the second fragment.

After this step when you open the nav_graph.xml and switch to the design tab, it should look like the following.

Navigation types

With the navigation component, we have multiple ways to navigate

1. Navigation using destination Id

We can provide the id of the destination fragment to navigate to it, like the following

button.setOnClickListener {
    findNavController().navigate(R.id.nav_second_fragment)
}

2. ClickListener

For views, we can also use createNavigateOnClickListener() method from the Navigation class as follows

button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.nav_second_fragment, null))

3. Navigation using Actions

As in the above nav graph, we have defined action in the first fragment, we can use the id of the action as follows

button.setOnClickListener {
    findNavController().navigate(R.id.action_first_to_second)
}

The last piece required is to define the NavHostFragment. It is a special widget that will display the different destinations defined in the nav graph. Copy the following code and paste it in the layout of the activity in which we want to load our FirstFragment.

<fragment
    android:id="@+id/nav_host_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:navGraph="@navigation/nav_graph"
    app:defaultNavHost="true"/>

android:name="androidx.navigation.fragment.NavHostFragment" defines the NavHostFragment used by NavController

app:defaultNavHost="true" is simply stating that you want this to be the NavHost that intercepts and works as the back button on your device.

app:navGraph="@navigation/app_navigation" associates the NavHostFragment with a navigation graph. This navigation graph specifies all the destinations the user can navigate to, in this NavHostFragment

After these steps when you run the app, FirstFragment should be loaded automatically and when you click the button it should open the SecondFragment. Also when you press the back button, it should navigate back to the FirstFragment.

That’s it. We have a working example of the Navigation Component. In the next section, we will discuss how to pass arguments while navigating to the fragments in a safe manner.

Safe Arguments

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 getting rid of the code like below

val username = arguments?.getString("usernameKey")

with the following

val username = args.username

Integration

Add the following code in the top-level Gradle file:

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.2.2"

Now import the plugin into the module level Gradle file:

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

Now the safe args plugin is active in your project. We will add 2 arguments to be passed to the SecondFragment from the FirstFragment. We will define arguments in the nav graph as follows

<fragment
    android:id="@+id/nav_second_fragment"
    android:name="app.navigationcomponentexample.SecondFragment"
    tools:layout="@layout/fragment_second">

    <argument
        android:name="arg1"
        app:argType="integer"
        android:defaultValue="0"/>
    <argument
        android:name="arg2"
        app:argType="string"
        android:defaultValue="default"/>

</fragment>

Here the first argument is named arg1 which is of type Integer and has the default value of 0. Similarly, the second argument is named arg2 which is of type String and has a default value of “default”.

After we define these arguments, Gradle will generate a class named SecondFragmentArgs which can be used in SecondFragment to retrieve the arguments in the following way.

val safeArgs: SecondFragmentArgs by navArgs()
val arg1 = safeArgs.arg1
val arg2 = safeArgs.arg2

Here we are assured that arg1 is of type Integer and arg2 is of type String and thus we don’t need to cast them to their respective types.

Now in order to pass these arguments from the FirstFragment, another class named FirstFragmentDirections gets created which has a static method named actionFirstToSecond. This can be used to pass the arguments in the following way

button.setOnClickListener {
    val directions = FirstFragmentDirections.actionFirstToSecond(arg1 = 1234, arg2 = "abcd")
    findNavController().navigate(directions)
}

That’s all is required to pass arguments in a type-safe manner. Apart from the inbuilt types, you can also define the custom type of arguments by creating a Parcelable class.

I hope you have found this article helpful. You can access the source code for the sample app from here.

Happy learning.