37

So I have this structure for my modules in my current app.

App Module Structure

I haven't found any official documentation on multi-module navigation yet but I found this article regarding this so here's how my gradle files are:

Feature 1 - Detail

...
implementation project(":base")
implementation project(":feature-2-detail")
...

Feature 2 - Detail

...
implementation project(":base")
implementation project(":feature-1-detail")
...

Feature 3 - Detail

...
implementation project(":base")
implementation project(":feature-1-detail")
...

And here are my navigation graphs:

Feature 1 - Detail

<navigation ...
    android:id="@+id/graph_feature_1_id">
    <include app:graph="@navigation/graph_feature_2" />
    <fragment ...
        android:id="@+id/nav_feature_1">
        <action ...
            app:destination="@+id/graph_feature_2_id" />

    </fragment>
</navigation>

Feature 2 - Detail

<navigation ...
    android:id="@+id/graph_feature_2_id">
    <include app:graph="@navigation/graph_feature_1" />
    <fragment ...
        android:id="@+id/nav_feature_2">
        <action ...
            app:destination="@+id/graph_feature_1_id" />

    </fragment>
</navigation>

Feature 3 - Detail

<navigation ...
    android:id="@+id/graph_feature_3_id">
    <include app:graph="@navigation/graph_feature_1" />
    <fragment ...
        android:id="@+id/nav_feature_3">
        <action ...
            app:destination="@+id/graph_feature_1_id" />

    </fragment>
</navigation>

So everything works with this kind of setup but the problem here is that to connect the module to another module, we have to add the other feature as a dependency to the current feature. Like in my case, Feature 1 - Detail can go to Feature 2 - Detail and vice versa and doing this gives me a circular dependency in gradle.

Is there another way to do multi-module navigation? I've tried using deep links but to no avail.

Any help would be appreciated! Thanks!

Kurt Acosta
  • 2,407
  • 2
  • 14
  • 29
  • 1
    Have you thought about using deep link for navigation? I was planning to start to refactor to a feature module setup, but I've encountered the same problem and the only solution I've though is using deeplink – Renato Almeida Oct 12 '18 at 17:00
  • For activities, I use deep links to perform multi-module navigation but for fragments, I'm still trying to find a solution. – Kurt Acosta Oct 15 '18 at 06:32
  • 1
    You can provide id of destination fragment with app module level dependency injection – Ali mohammadi Oct 21 '18 at 13:26

3 Answers3

32

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!

Kurt Acosta
  • 2,407
  • 2
  • 14
  • 29
  • 1
    really appreciate it what you've done. your article helps me so much – mochadwi Mar 21 '20 at 12:52
  • have you had any issues with back navigation? i'm experiencing this issue where i navigate from screen A (app module) to B (module B) and then to C (module C). when i C, back navigation stops working. however, if i navigate directly from A to C, works just fine. – takecare Jun 13 '20 at 18:24
  • @Kurt Acosta How can I send a Parcelable object from Feature1 to Feature2? – prashant17 Jul 14 '20 at 11:58
  • Hi @Kurt, Please tell do we need to define these uri in manifest or build.gradle ? I just followed your tutorial and it didn't work for me. – Munazza Aug 25 '20 at 17:09
  • 1
    I just added deeplinks in navigation graph and the tried to navigate from one fragment to other in code. But I get following error. Navigation destination that matches request NavDeepLinkRequest{ uri=app://verily/LoginFragment } cannot be found in the navigation graph NavGraph(com.app.verily:id/splash_navigation) startDestination={Destination(com.app.verily:id/splashFragment) label=Splash class=com.app.launch.splash.SplashFragment} Please help! – Munazza Aug 25 '20 at 21:07
  • @mochadwi please share some working code template . My deep links are not working. – Munazza Aug 25 '20 at 21:09
  • my deep link also not working in module-module communication. getting error like- Navigation destination that matches request NavDeepLinkRequest{ uri=news://newslist } cannot be found in the navigation graph NavGraph(com.rp.th.uat:id/nav_home) startDestination={Destination(com.rp.th.uat:id/homeFragment) label=Home Screen class=com.rp.home.presentation.view.fragments.HomeFragment} – sweet_vish Sep 19 '20 at 18:21
  • How can I control the `popUpTo` behavior with this solution? – A1m Sep 23 '20 at 18:42
  • @A1m are you able to find URI of different module? – sweet_vish Sep 28 '20 at 18:33
  • It is a nice workaround but it does not feel like a natural solution. I hope, they would address this issue more elegantly. – Yekta Sarıoğlu Dec 06 '20 at 15:10
  • For the ones who gets NavDeepLinkRequest cannot be found in the navigation graph error, navigation component needs to map those graphs in somewhere to being aware of destinations. So if you have an app dependent on other modules, open your app nav_graph and include other graphs like ``. After this you can navigate to target URI – volsahin Feb 28 '21 at 20:15
8

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.

doolle89
  • 171
  • 1
  • 5
3

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)
laenger
  • 991
  • 1
  • 10
  • 20
  • Though this solution could work, I believe the app or instant app module would be where the features would be merged together. base module just provides the common code to the feature modules. With activities, I can easily navigate from module to module through deep/app links but with fragments, I can't seem to find a workaround. – Kurt Acosta Aug 14 '18 at 13:33
  • For navigating between fragments of otherwise independent features, the above setup is my current workaround. It is true though that bundling `feature-one` and not `feature-two` in the app module will cause a runtime crash when attempting to navigate from `feature-one` to `feature-two`. If this were to be expected, this would have to be handled in code. I'd also be very interested in alternative approaches. – laenger Aug 14 '18 at 14:30
  • I just added the app/build.gradle to the answer as it was missing before. – laenger Aug 14 '18 at 14:37
  • If this is the case, then I guess the `app/build.gradle` would only just need to get the base module since the base module already has everything. This would work but like you said, it's not a 100% satisfying solution. Anyhow, I'll go with this solution first! Thanks for your suggestion! – Kurt Acosta Aug 15 '18 at 03:29
  • Please be aware that the dependency you describe is `feature project(...)` and not `implementation project(...)`. Only including `feature-base` in the app/build.gradle will not include other features automatically (btw adding an implementation dependency from base to feature would create a circular dependency). Also see [here](https://github.com/googlesamples/android-instant-apps/blob/master/hello-feature-module/features/base/build.gradle#L50) and [here](https://github.com/googlesamples/android-instant-apps/blob/master/multi-feature-module/base/build.gradle#L58). – laenger Aug 15 '18 at 08:34
  • @KurtAcosta, can u please share the template of this , or please let me know how you are using application (":app") in build.gradle – Reprator Jun 30 '19 at 10:14