Navigation: Nested graphs and include tag

Murat Yener
Android Developers
Published in
4 min readMay 10, 2021

--

Here we are with the third article of second Navigation series. If you prefer to watch this content instead of reading, check out the video below:

Intro

In the previous articles in this series, we added coffee tracker functionality, enhanced the user experience with navigation UI and implemented conditional navigation.
This time we’ll see how to organize the navigation graph by using nested graphs and using the include tag to import other graphs. This will allow us to modularize the app and see how navigation works with modules.
Ok time to fire up Android Studio and see how to use navigation with modules.

Nested Navigation Graphs

Let’s start with the navigation graph. Nested graphs can help you group a series of destinations within a parent navigation graph.
Looking at the navigation graph, the coffeeList and coffeeEntryDialog destinations are good candidates to convert into a nested graph. To do that, I select both fragments by holding shift and select move to nested graph.

Move coffeeList and coffeeEntryDialogFragment to a nested graph

Now, switching to code view, you can see that a nested graph is simply a new navigation graph inside the root graph.

<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/donutList">

<fragment
android:id="@+id/donutList"
android:name="com.android.samples.donuttracker.donut.DonutList"
android:label="@string/donut_list" >
<action
android:id="@+id/action_donutList_to_donutEntryDialogFragment"
app:destination="@id/donutEntryDialogFragment" />
<action
android:id="@+id/action_donutList_to_selectionFragment"
app:destination="@id/selectionFragment" />
</fragment>
<dialog
android:id="@+id/donutEntryDialogFragment"
android:name="com.android.samples.donuttracker.donut.DonutEntryDialogFragment"
android:label="DonutEntryDialogFragment">
<deepLink app:uri="myapp://navdonutcreator.com/donutcreator" />
<argument
android:name="itemId"
app:argType="long"
android:defaultValue="-1L" />
</dialog>
<fragment
android:id="@+id/selectionFragment"
android:name="com.android.samples.donuttracker.setup.SelectionFragment"
android:label="@string/settings"
tools:layout="@layout/fragment_selection" >
<action
android:id="@+id/action_selectionFragment_to_donutList"
app:destination="@id/donutList" />
</fragment>
<navigation
android:id="@+id/coffeeGraph"
app:startDestination="@id/coffeeList">
<fragment
android:id="@+id/coffeeList"
android:name="com.android.samples.donuttracker.coffee.CoffeeList"
android:label="@string/coffee_list">
<action
android:id="@+id/action_coffeeList_to_coffeeEntryDialogFragment"
app:destination="@id/coffeeEntryDialogFragment" />
</fragment>
<dialog
android:id="@+id/coffeeEntryDialogFragment"
android:name="com.android.samples.donuttracker.coffee.CoffeeEntryDialogFragment"
android:label="CoffeeEntryDialogFragment">
<argument
android:name="itemId"
android:defaultValue="-1L"
app:argType="long" />
</dialog>
</navigation>

</navigation>

Navigation between the selected fragments is moved into the nested graph.
Nested graphs must have an id. You can use this id to create actions to navigate to the nested graph but not directly to its child destinations. Nested graphs have their own start destination and don’t expose their child destinations separately.

<navigation
android:id="@+id/coffeeGraph"
app:startDestination="@id/coffeeList">

If you double click on the nested graph then you can see the nested destinations and actions between them.

Include tag

Instead of using nested graphs, I can also extract the graph into a new navigation xml file. I create a new xml file called coffee_graph and move the contents of the nested graph into this new file.

<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/coffeeGraph"
app:startDestination="@id/coffeeList">
<fragment
android:id="@+id/coffeeList"
android:name="com.android.samples.donuttracker.coffee.CoffeeList"
android:label="@string/coffee_list">
<action
android:id="@+id/action_coffeeList_to_coffeeEntryDialogFragment"
app:destination="@id/coffeeEntryDialogFragment" />
</fragment>
<dialog
android:id="@+id/coffeeEntryDialogFragment"
android:name="com.android.samples.donuttracker.coffee.CoffeeEntryDialogFragment"
android:label="CoffeeEntryDialogFragment">
<argument
android:name="itemId"
android:defaultValue="-1L"
app:argType="long" />
</dialog>
</navigation>

I can nest this new graph in the other one using the include tag. Using the include tag offers the same functionality as nested graphs but lets you use graphs from other modules or libraries.

<include app:graph="@navigation/coffee_graph"/>

Similar to nested graphs, included graphs don’t expose the list of destinations in the included graph which means I need to update the menu ids which refer to coffeeList.

<item
android:id="@id/coffeeGraph"
android:icon="@drawable/coffee_cup"
android:title="@string/coffee_name" />

I update the menus to use the included graph’s id. Since CoffeeList is the startDestination of the included graph, I can use the graph id to navigate to this graph. If you try the app now, everything should be working as before.
Now that the navigation graph for coffee tracking is separate this is a good time to modularize the app and see how navigation works with modules.
If you want to follow along you can check out this repo which has all the changes I did so far.
I create 2 new modules: core and coffee. I move all common classes such as Donut, Coffee, DAOs, Database and other common resources to the core module.
Next, I move all fragments, viewModels and adapter classes used in coffee tracking into the coffee module. Layouts and other resources used in coffee tracking are also moved here, as well as the coffee_graph.

Existing classes and resources are moved to core and coffee modules

Coffee module depends on core module.

dependencies {

implementation project(":core")
//...
}

Finally, in the app module, add coffee and core modules as a dependency.

dependencies {
implementation project(":coffee")
implementation project(":core")
//..
}

Note that nothing changes in the navigation graph; it is not affected from these changes

No changes in the navigation graph

Now if I run the app everything works as it used to but using modules instead! You can check out the final code here.

With this change I separated the coffee tracker module and it’s navigation flow from the rest of the app which means the coffee tracker module can be used independently from the donut tracker.

Summary

In this article we’ve seen how to create nested graphs and how to use the include tag to modularize the donut tracker app.

Next article, we’ll take this further and learn how to use navigation with feature modules. Stay tuned!

--

--