Let's Navigate
Tags: android, navigation, di, multimodule
Published: 2021-03-08 09:01:45

As Android Engineers probably many of you have already approached the development of a multi-module project and, if not, please consider doing that an maybe check out this cool article by Joe Birch.

{% medium https://medium.com/google-developer-experts/modularizing-android-applications-9e2d18f244a0 %}

If you already had some experience with this topic, you probably know that one of the most challenging problems of a multi-module project is how to approach the **navigation** between various feature modules.

There are tons of possible approaches around this topic and here I'm going to talk about one based on **Navigation Component** from Google.

Suppose that you have this situation:
One **Application Module** and a few other **feature modules**.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2524bipqvkdwi2dijanu.png)

At this point let's say that we want to navigate from `feature 1` -> `feature 2`.

The first thing that you maybe be tempted to do is to introduce one new dependency in the Gradle from `feature 1` -> `feature 2` in order to see the destination that you want to reach.

Well, this is not a good practice because later on time can happen that you will have the need to navigate back from `feature 1` <- `feature 2` and, once you introduced this new dependency, you will have a circular dependency in your project.

![gif](https://media.giphy.com/media/fSkpUE72ynxPsKVNYW/giphy.gif)

Now that we understood why it's so complex to navigate between different modules we can try to find some good solution to this problem with the help of **Navigation Component**.

In this post, I'm going to assume that you have, at least, a basic knowledge of what is *Navigation Component* and what is capable to do.
Trying to describe it in the simplest way possible, we can say that *Navigation Component* is a tool that allows you to declare all your `destinations` and `routes` in one `XML`, called `Navigation Graph`, and provides a high-level API to navigate between them.
One feature that, in this case, can help us a lot is the ability to split the content of one monolithic navigation graph into multiple ones.

Now that we have these concepts, let's summarize all the goals that we want to achieve with our navigation implementation:

- *Every feature should be responsible for its own graph.*
- *Every feature should be able to reach all the available destinations inside the app.*
- *Avoid Circular Dependencies.*

**Well! Now it's finally time to code!**

![gif](https://media.giphy.com/media/BpGWitbFZflfSUYuZ9/giphy.gif)

The first thing that we have to do is to declare all the `Navigation Graphs` for every feature so we will end in a situation like this:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0z5m0zsiokgrpmosw5y.png)

In this way, inside the `main_graph.xml`, should be possible to do something like this:

```
<include app:graph="@navigation/feature_1_graph.xml" />

<include app:graph="@navigation/feature_2_graph.xml" />

<include app:graph="@navigation/feature_3_graph.xml" />
```

Doing so, every feature will be responsible for its own internal `Navigation Graph` and should expose only the entry points needed to navigate to the feature.
All the `sub-graphs` are then collected by the application and included inside the `main_graph` to allow all the features to perform the navigation.

Now we have only another problem: **How to perform the navigation?**

In order to navigate with *Navigation Component* we need two things:
- *The destination that we want to reach is included in the `main_graph.xml`.*
- *The ID of the entry point of the destination.*

The first point is done so we need to focus on the second one.

To solve this problem we can use one small trick:
**Declare in advance all the entry points IDs in a shared place**

`destinations.xml`
```
<resources>

    <!--
    This resource file is the place where you should declare public navigable destinations inside the app
    -->

    <item name="feature1Fragment" type="id" />
    <item name="feature2Fragment" type="id" />
    <item name="feature3Fragment" type="id" />

</resources>
```

In this way, we can use the predeclared IDs to later provide the implementation of the destinations and/or new routes that are using the IDs of the destinations.

`feature_1_graph.xml`
```
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/feature_1_graph"
    app:startDestination="@id/feature1Fragment">

    <fragment
        android:id="@id/feature1Fragment"
        android:name="com.navigation.sample.feature1.Feature1Fragment"
        android:label="Feature1Fragment">

        <action
            android:id="@+id/feature1ToFeature2"
            app:destination="@id/feature2Fragment" />

    </fragment>

</navigation>
```

At this point your Gradle dependency graph should look like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shvvva8kp6ab0mr84uq4.png)

Now let's check our goals list to see how many progress we did so far:

- *Every feature should be responsible for its own graph.* ✅
- *Every feature should be able to reach all the available destinations inside the app.* ✅
- *Avoid Circular Dependencies.* ✅

As you can understand from the goals list, the solution that we implemented is satisfying all the requirements that we fixed but there is one thing that we can still improve optionally.

This solution has one requirement:
Every time that we want to add or remove a feature we have to manually modify both the `main_graph.xml` and the `destinations.xml` to update the included features properly.

Not a big problem in the end but since developers are lazy we can try to find a better solution for this problem. 😝

But what can we do about it? 🤔

The idea here is to collect all the `sub-graphs` references and include them programmatically to the `main_graph.xml` at runtime.

To reach this result we are going to use the `Dependency Injection`.

Here doesn't matter if you prefer to use Dagger2, Koin or whatever.
The important thing is that in every feature you should be able to expose the navigation graph reference of the feature outside of its own module.

Let's see a quick sample:

```
@Module
object Feature1GraphModule {

    @Provides
    @IntoSet
    fun provideGraphReference() = R.navigation.feature_1_graph
}
```

Next, you should be able to access all these graphs references and include them in the `main_graph` with a code like this:

```
val mainGraph = navController.navInflater.inflate(R.navigation.main_graph)

// subGraphsIDs is the set of all the sub-graphs collected by Dagger multi-binding

subGraphsIDs
   .map { graphId ->
      navController.navInflater.inflate(graphId)
   }
   .forEach { graph->
      mainGraph.addAll(graph)
   }
    
navController.graph = mainGraph
```

This operation should be performed in your main `NavController` in order to allow the entire app to see your `sub-graphs`.

Now, to wrap up let's check our Gradle dependency graph one last time:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g9f76sc1zyiy4e7zeodr.png)

As you can see now we have a new module called `main_activity_feature`.
This is where your `MainActivity`/`Main NavController` should be located.
In this module, you should perform the setup that I described before.

Another small difference is that now the `app` module doesn’t know anything about the `main_graph`, about the navigation or anything else.
Now the `app` module is depending on the feature modules only for collecting all the `Dependency Injection Modules` and this is a big plus in terms of code maintenance because, in this way, every time you need to add or remove a feature you just need to provide the correct DI setup and you are done!

![gif](https://media.giphy.com/media/xUPOqo6E1XvWXwlCyQ/giphy.gif)

A working sample of this navigation approach can be found in my personal project here:

{% github harrypulvirenti/TimeToPlay no-readme %}

I hope that you liked this small post and thanks for reading till here!