How to use Jetpack Navigation in multi module projects
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 theNavHost
, 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.