Getting familiar with Navigation Components

Ashish Kumar
6 min readJul 23, 2019

--

Playing with fragment transactions and handling relevant states has always been (or at least used to be) something that the developers are afraid of. To the rescue comes, the Navigation Components in android which has now reached a pretty stable state and we should all consider using it in our applications. With features like, automatic handling of fragment transactions, deep linking as first class operations and moreover a tool (in the latest update of Android Studio {3.3 and higher}) for visualising and editing the navigation flow of the app, the architecture components could really help us in simplifying navigation.

To keep things simpler, we will start with a very basic application, a movies application which will have just two features, show a list of movies in the first (entry) page, the user action (here onClick) would take us to the next page, the movie details page. Just to make sure that we do not deviate from the main point of discussion, I will not be discussing the other details, about how to populate either of the screens.

To start with, lets discuss the architecture of the app. We have a single Activity with two fragments. For simplicity, lets name them Fragment A and Fragment B. Fragment A displays the list of movies and Fragment B shows the details of the corresponding movie. Our aim is to achieve that when the app is opened, by default Fragment A is shown and the user action on one of the list items of Fragment A, should show Fragment B. Also, the back press of Fragment B should take us back to Fragment A.

Before we go into the implementation details, let us understand the three key parts of Navigation, ie. the Navigation Graph, the NavHostFragment and the NavController. Let us discuss each one of them briefly

The Navigation Graph :

In the resources directory of you app, create a new directory, named as navigation, where you add the xml file which is referenced by the navigation architecture framework to build your navigation graph. This file includes all the places in the app which we name as destinations — all the possible paths, a user could take through your app.

The navigation graph is similar to a typical resource file in android, except that it would reside in the navigation directory in your resources folder. To add the navigation graph to your project, right click on your res ->New -> Android Resource File, name the file as base_nav_graph (of course you can choose any name) and in the drop down associated with the resource type, select navigation as shown below.

When adding a navigation graph to your project, Android Studio displays a prompt and offers to add the dependencies for you. Help yourself to add the below mentioned dependencies either way

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

You can find the latest value of navigationVersion here.

For our use case, we have two destinations, Fragment A and Fragment B. Add the following lines in your base_nav_graph.xml

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

<fragment
android:id="@+id/fragment_a_dest" android:name="com.example.android.testapp.navigation.MoviesListFragment"
tools:layout="@layout/fragment_a">

<action
android:id="@+id/item_click_action"
app:destination="@+id/flow_step_two_dest">
</action>
</fragment>
<fragment
android:id="@+id/fragment_b_dest" android:name="com.example.android.testapp.navigation.MovieDetailsFragment"
tools:layout="@layout/fragment_b">
/>
</fragment>
</navigation>

Let us try to understand what each one of these attributes mean to us.

<navigation> is the root of any navigation which contains one or more destinations, represented by <activity> or <fragment>. In our case, as our destinations are fragments, I have added two <fragment> tags. You must have noticed the attribute, app:startDestination="@+id/fragment_a_dest". This attribute is really important, as it decides the destination which is loaded by default when the user first opens the app and in our case we have given it, the id of the destination-Fragment A.

Let us now look at our destination tags, in our case <fragment> , which has three major attributes (of course there are other attributes too, like <action>, <argument>, and <deepLink>)

<android:id>— which denotes the id of the destination which is used by the NavigationController(which will be discussed below) to identify the destination.

<android:name> — the fully qualified class name of the destination, in our case our fragments

<tools:layout> — the layout file of the destination, which would be used by the android studio to show the destination while in the graphical editor.

One of the other attributes which you would have noticed is <action> , the details of which we will discuss along with the NavController.

The NavHostFragment :

The NavHostFragment as the name suggests, is the host, an empty container where destinations are swapped in and out as the user navigates through the app. As the official documentation says, The Navigation component is designed for apps that have one main activity with multiple fragment destinations. The main activity is associated with a navigation graph and contains a NavHostFragment that is responsible for swapping destinations as needed. In an app with multiple activity destinations, each activity has its own navigation graph.

In the layout file of the activity, along with the other things, we need to add the NavHostFragment as shown below

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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"
tools:context=".MainActivity">

<!-- Other ui elements-->

<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/base_nav_graph" />
<!-- Other ui elements--></android.support.constraint.ConstraintLayout>

In the above lines of code, there are few things to notice

<app:defaultNavHost="true"> — This attribute ensures that this NavHostFragment will intercept our system back press event. To be more clear, say we have multiple NavHostFragment’s in your layout, we need to specify the default NavHostFragment which has the ability to intercept the default system back press.

<android:name> — This attribute is used to identify the NavHost implementation which in our case is

android:name=”androidx.navigation.fragment.NavHostFragment”

and lastly

<app:navGraph> — which specifies the resource id of of the nav graph, in our case named as the base_nav_graph.xml.

I had intentionally not asked you to see the design, the graphical editor section of the base_nav_graph.xml, as we had not introduced the NavHostFragment then. And now that we have, I would like you to double click the base_nav_graph.xml and click on the design tab. You should see something like this.

The NavController :

NavController is the object which manages the navigation within a NavHost.

To retrieve the NavController, you can use either one of these, based on the context you are in

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

Once you have the NavController object, you can use either of them to perform the navigation. In our case we have to navigate to Fragment B when any of the list items in Fragment A is clicked

itemView.setOnClickListener { view ->
view.findNavController().navigate(R.id.item_click_action)
}

Notice, the id passed to the navigate function, is the same one that we had mentioned in the nav graph, the one the framework uses to identify the associated action (which in our case is to navigate to Fragment B)

But wait a minute, aren’t we supposed to pass an argument to Fragment B, the identifier of the movie details to be displayed. Don’t worry, the NavController supports this as well. Modify the above function as below,

view.setOnClickListener { view ->
run {
val bundle = Bundle()
bundle.putInt("movie_id", 1)//The corresponding movie id

view.findNavController().navigate(R.id.item_click_action, bundle)
}
}

Just like we receive the arguments in any fragment, we can retrieve the arguments in our Fragment B, using the relevant key. As simple as it looks, we have just setup our very basic app using the navigations architecture components.

References

https://developer.android.com/guide/navigation/navigation-getting-started

--

--

Ashish Kumar
Ashish Kumar

Written by Ashish Kumar

Engineer @Amazon | Open Source Enthusiast | Mentor Google Summer of Code’19 | Views are my own

No responses yet