How to use Jetpack Navigation in multi module projects

João Gabriel
4 min readJul 9, 2020

--

What is Jetpack Navigation?

The Navigation is an Android Jetpack component that helps you implement navigation, from simple button clicks to more complex patterns, such as app bars and the navigation drawer.

The Navigation component consists of three key parts :

  • NavGraph : XML that contains all navigation information.
  • NavHost : Empty contatiner that shows destination graphs.
  • NavControler : Object that manages the app navigation inside a NavHost. The NavController helps exchanging data between destinations on the NavHost, as the users navigate through the app.

For more information : https://developer.android.com/guide/navigation

Why use Jetpack Navigation

With the Navigation is possible to see all app navigation through the NavGraph, therefore it is not necessary to search for each Intent andFragmentTransaction in the project to find all possible destinations.

It is no longer necessary to worry about the fact that the back button exits the application when coming from a deep link and talking about deep link, they also have this navigation information.

Security when passing arguments using SafeArgs. It is no longer necessary to perform security checks and protections, because it is possible to have arguments with defined types.

It also works with current standards for the navigation user interface, as bottom nav, with just a few settings and it can handle transitions and animations 💙 between navigation.

Prerequisites

To use Navigation it is necessary to have installed Android Studio at version 3.3 or higher.

And add to the project’s build.gradle the navigation dependencies:

implementation "androidx.navigation:navigation-fragment-ktx:2.2.2"
implementation "androidx.navigation:navigation-ui-ktx:2.2.2"

this is the current version of navigation

Hands on

In monolithic projects we have all features and layers in a single module, but not all features need each other, this also applies to navigation.

As an example we have a feature 1 that has 2 fragments and navigates from Fragment A → Fragment B

Using navigation, we will only need a NavGraph with our 2 destinations, and an action that go from Fragment A to Fragment B

<?xml version=”1.0" encoding=”utf-8"?>
<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_feature_one”
app:startDestination=”@id/fragment_a”>
<fragment
android:id=”@+id/fragment_a”
android:name=”io.jgabriel.featureone.FragmentA”>
<action
android:id=”@+id/action_go_to_b”
app:destination=”@+id/fragment_b”
app:popUpTo=”@+id/fragment_a” />
</fragment>
<fragment
android:id=”@+id/fragment_b”
android:name=”io.jgabriel.featureone.FragmentB”/>
</navigation>

So we have the navigation of our first feature.

Now we are going to increase our application by including 2 more feature modules:

Our application now contains these features and fragments:

It will also be necessary to add NavGraphs for new features created following the same model as Feature 1

<?xml version="1.0" encoding="utf-8"?>
<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_feature_two"
app:startDestination="@id/fragment_c">
<fragment
android:id="@+id/fragment_c"
android:name="io.jgabriel.featuretwo.FragmentC">
<action
android:id="@+id/action_go_to_d"
app:destination="@+id/fragment_d"
app:popUpTo="@+id/fragment_c" />
</fragment>
<fragment
android:id="@+id/fragment_d"
android:name="io.jgabriel.featuretwo.FragmentD"/>
</navigation>
<?xml version="1.0" encoding="utf-8"?>
<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_feature_three"
app:startDestination="@id/fragment_e">
<fragment
android:id="@+id/fragment_e"
android:name="io.jgabriel.featurethree.FragmentE">
<action
android:id="@+id/action_go_to_f"
app:destination="@+id/fragment_f"
app:popUpTo="@+id/fragment_e" />
</fragment>
<fragment
android:id="@+id/fragment_f"
android:name="io.jgabriel.featurethree.FragmentF" />
</navigation>

Now we need to add our features to the application module using build.gradle

implementation project(":featureone")
implementation project(":featuretwo")
implementation project(":featurethree")

Now our application module knows our 3 features.

<?xml version="1.0" encoding="utf-8"?>
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_main"
app:startDestination="@id/nav_feature_one">
<include app:graph="@navigation/nav_feature_one" />
<include app:graph="@navigation/nav_feature_two" />
<include app:graph="@navigation/nav_feature_three" />
</navigation>

It is now possible to browse any fragment within the application module.

In real problems, we usually need to access a fragment of a feature, through another one for example:

Fragment B which is in Feature 1, needs to navigate to Fragment D which is in Feature 2:

For this we will add deep links to the fragments and do the navigation through them, so the features will remain independent and the app module will be responsible for managing this navigation.

New feature_two navGraph

<?xml version="1.0" encoding="utf-8"?>
<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_feature_two"
app:startDestination="@id/fragment_c"
tools:ignore="UnusedNavigation">
<fragment
android:id="@+id/fragment_c"
android:name="io.jgabriel.featuretwo.FragmentC">
<action
android:id="@+id/action_go_to_d"
app:destination="@+id/fragment_d"
app:popUpTo="@+id/fragment_c" />
</fragment>
<fragment
android:id="@+id/fragment_d"
android:name="io.jgabriel.featuretwo.FragmentD">
<deepLink app:uri="myApp://fragmentD" />
</fragment>
</navigation>

Using the deepLink tag, we can specify the URI that we can use to navigate to that specific destination, without Feature 1 needing to know our Feature Two.

To do this navigation we will add a click on FragmentB that will navigate to FragmentD.

class FragmentB : Fragment() {
fun onViewCreated(...) {
...
...
view.setOnClickListener {
val uri = Uri.parse("myApp://fragmentD")
findNavController().navigate(uri)
}
}
}

And with that we keep our modules independent of each other but able to navigate among themselves.

Thanks for ready, all code is available in GitHub.
Special thanks to MovilePay’s android team.

--

--