Multi-module Navigation with Architecture Components
It is possible to remove all Gradle inter-feature dependencies when you declare each feature nav graph ID explicitly in the base feature. I am not 100% satisfied with this solution since these IDs create "hidden" inter-feature dependencies but otherwise it works fine.
Here are the key parts of this setup:
:app
build.gradle
dependencies {
implementation project(':features:feature-base')
implementation project(':features:feature-one')
implementation project(':features:feature-two')
}
:features:feature-base
build.gradle
dependencies {
application project(':app')
feature project(':features:feature-one')
feature project(':features:feature-two')
}
navigation/feature_base_nav_graph.xml
<navigation ...>
<include app:graph="@navigation/feature_one_nav_graph" />
<include app:graph="@navigation/feature_two_nav_graph" />
</navigation>
values/feature_base_ids.xml
<resources>
<item name="feature_one_nav_graph" type="id" />
<item name="feature_two_nav_graph" type="id" />
</resources>
:features:feature-one
build.gradle
dependencies {
implementation project(':features:feature-base')
}
navigation/feature_one_nav_graph.xml
<navigation
android:id="@id/feature_one_nav_graph"
...>
<fragment
android:id="@+id/oneFragment"
...>
<action
android:id="@+id/navigateToFeatureTwo"
app:destination="@id/feature_two_nav_graph"
... />
</fragment>
</navigation>
navigate
findNavController().navigate(R.id.navigateToFeatureTwo)
:features:feature-two
build.gradle
dependencies {
implementation project(':features:feature-base')
}
navigation/feature_two_nav_graph.xml
<navigation
android:id="@id/feature_two_nav_graph"
...>
<fragment
android:id="@+id/twoFragment"
...>
<action
android:id="@+id/navigateToFeatureOne"
app:destination="@id/feature_one_nav_graph"
... />
</fragment>
</navigation>
navigate
findNavController().navigate(R.id.navigateToFeatureOne)
One of the approaches that might be useful is to create a completely new independent module (e.g ":navigation" module) and move all navigation.xml files from all other modules to it. Then we depend on that new (":navigation") module in all other modules where navigation related stuff is needed, and we will be able to access its R.navigation or generated argument classes, etc.
Since the new (":navigation") module doesn't know about anything else in the project IDE will mark red any fragment, activity and other classes we use in navigation.xml files, that are defined outside in other modules but as long as we use full class names (com.exampel.MyFragment) it will compile and work.
<?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_graph_id"
app:startDestination="@id/some_navigation_id">
<fragment
android:id="@+id/some_navigation_id"
android:name="com.exampel.MyFragment".../>
// com.exampel.MyFragment will be marked red since IDE can't link it
// to the existing class because it is in the other module
This creates "hidden" dependency to all classes we want to navigate to in a way that we need to know class names and potentially arguments, and we have to maintain it manually but it allow us to easily separate navigation in independent module.
This is already a year-long but the library now can support this exact use-case! As of 2.1.0-alpha03, we can navigation through deep link URIs.
Instead of adding the features as implementation details to each other, we can leave them unaware between themselves and use deep link navigation.
Feature 1 - Detail - build.gradle
dependencies {
implementation project(':base')
}
Same with Feature 2 - Detail. No need for it to know the other modules.
To have inter-module navigation, we have to first define the deep link for navigating through that destination via a deepLink
tag.
Feature 1 - Detail - Navigation Graph
<navigation ...
android:id="@+id/graph_feature_1_detail_id">
<fragment ...
android:id="@+id/nav_feature_1_detail">
<deepLink app:uri="myApp://feature1detail"/>
</fragment>
</navigation>
Feature 2 - Detail - Navigation Graph
<navigation ...
android:id="@+id/graph_feature_2_detail_id">
<fragment ...
android:id="@+id/nav_feature_2_detail">
<deepLink app:uri="myApp://feature2detail"/>
</fragment>
</navigation>
Now that we have deep links with URIs set, we can directly use this in a NavController
So in the fragment in Feature 1 - Detail, maybe on a button click? Anywhere where you have to perform navigation
class Feature1DetailFragment {
fun onViewCreated(...) {
...
view.setOnClickListener {
val uri = Uri.parse("myApp://feature2detail")
findNavController().navigate(uri)
}
}
}
And in Feature 2 - Detail,
class Feature2DetailFragment {
fun onViewCreated(...) {
...
view.setOnClickListener {
val uri = Uri.parse("myApp://feature1detail")
findNavController().navigate(uri)
}
}
}
And voila! Inter-module navigation.
At the time of writing, the latest stable release is 2.1.0-rc01
.
Although I haven't tried this out on more complex projects, I love this library and I'm hoping to see this library mature more!
I created a Medium article about this. You can take a look at it. Cheers!