Android Multimodule Navigation with the Navigation Component
Using the navigation component for a simple single-module application is easy. Just create your nav graph, set your start destination and give the nav graph to the NavHostFragment and voila, it’s all ready to go!
However when your feature modules are decoupled from each other it’s not so easy to implement.
This article is not meant for people who are just starting out with the Navigation Component. I assume you have some basic knowledge of it and/or have already tried to implement this for a multi-module application. If not then you might have a hard time grasping some things said here. If you are just starting out with the NavComponent this guide may not be for you!
I have created a small POC project to illustrate the approach. Here is a link to the repo: https://github.com/DDihanov/android-multimodule-navigation-example
UPDATE 20.04.2021: Since some people requested an example of accessing nested destinations, I updated the repo with a simple deeplink example that navigates from a nested destination to a destination in another module and vice versa.
After spending hours of refining and thinking of different approaches to the problem of multi-module navigation — this is the solution I went with, since it feels intuitive and you can use the navigation editor and SafeArgs! It encapsulates the navigation logic in a separate navigation module and allows for easy expansion. The project has only the basic things it needs to work but can be taken as an example and further expanded for your needs.
The navigation graphs of the app look like this for the separate modules:
commonui: StartFragment
home: HomeFragment -> NextFragment
dashboard: DashboardFragment
The example demonstrates navigation between the different flows and the navigation in the home module:

But first a brief overview of our use-case so you can understand the predicament better:
The app is structured like so:

We have a main app module which is there only to connect dependencies together and will host our Application class(no application class in this case). In my projects I like to have my main app module as simple as possible, containing no UI or any other type of logic.
Now a brief description of each module:
The app module has all the other modules as dependencies in order to be able to create the DI graph(no DI graph in this case, but in principle, the feature modules provide their dependencies and the app module just wires them together. I have included home, commonui and dashboard as dependencies in the app.gradle for good practice).
The commonui is a module which should ideally contain all common UI logic of the app, be that strings, drawables, BaseFragment and BaseActivity classes, common compound views, etc… In this case it contains the single activity(MainActivity)
The dashboard and home(they have commonui as dependency, and ideally a domain module if you are using CLEAN) are both feature modules which contain only the needed fragments and encapsulate a navigation flow in a navigation subgraph. They handle all the navigation done in the specific feature module themselves. When they need to switch to another navigation flow, they will use the Navigator class in the navigation module.
The navigation module contains, well as the name suggests, the means of navigation in our app and will be where the magic happens. The navigation
module does not depend on any other module. It contains the main navigation graph for the app.
The navigation of the app will consist of a main navigation graph xml file and subgraphs each located in every feature module. Switching between navigation flows should be done in the navigation module.
Now on to the predicament:
Since the navigation module is the one that handles cross-module navigation, it needs to know all the subgraphs of the other modules in order to navigate. So how do we supply the submodule navigation graphs to the main navigation graph in order to use the Navigation Component, and without creating any circular-dependencies in the code?
There are a few solutions to this:
- Supplying the subgraphs from the feature modules via dependency injection at runtime, inflating them and adding them programatically to the main nav graph.
- Using only one navigation graph and adding all the fragments inside it, (like this example — note how the navigation module hardcodes the paths to the fragments inside of it’s graph). This approach will work, however it will display an error in the navgraph compile time, since the paths to the fragments can’t be resolved, but since the resources get squashed down at runtime — it will work. Downside is that you can’t use safeargs with this approach and it seems kinda code-smelly(to me at least)
- This approach — defining the bare submodule navigation graphs inside the navigation module, and then implementing them in each of the submodules using the pre-defined IDs in the navigation module(kinda like defining an interface and then implementing it with a class). This seems confusing but it will become clear in a second. With this approach, safe args work, you can use the nav editor, and the flow-switching logic is contained inside of the navigation class.
…or alternatively you can use deep-linking for multi-module navigation(however I didn’t find success with this approach)
Let’s see the code!
Here are some screenshots of how the modules look inside:




Let’s see how the main navigation graph looks like:
It has a start destination, which is base_flow and it contains all other flows from the other feature modules and two actions that allow us to navigate from one flow to another.
But wait… The navigation module doesn’t depend on any other module, how are the nav graphs resolved? Glad you asked!
First we need to create empty navigation files in the navigation module so the compiler stops complaining:

These files look like this inside:
They are all empty, and have only IDs. The IDs are predefined in:

So right now we gave the main navigation graph and a set of empty navigation files. Great! We have built kind of an empty shell of what our navigation will look like without any concrete implementation. Next we need to link the empty navigation files we created with the real ones!
Since our feature modules depend on the navigation module, we have access to the ids we created in the nav_ids.xml file. So in order to link them, we need to create a navigation res file with the same name as the empty navigation graph file in the navigation module:

Here are the two files:
You might have noticed we also have a base_flow.xml in commonui. What is this file you ask?
Well this is the flow which the app will use to first start. In this case we load up the StartFragment first and then decide where to navigate from there. In a case where we might have a login screen and the user has reopened the app, but their login token is still valid, or we need to load some other resources first, this screen will be useful. Both of these files are located inside the commonui module:
I will repeat this once again — the android:id we used on the now expanded graphs are the same as the ids we defined in the nav_ids.xml file in the navigation module.
Once we have linked all the IDs and navigation graphs together. We can take a look at the Navigator file.
The navigator uses a NavigationFlow sealed class to represent each navigation flow:
There is also an interface which the MainActivity implements:
The logic here is pretty simple.
We only need to set the navController to the Navigator class, and implement the interface:
Something to note here — in reality you would want to inject the Navigator with your DI framework and not directly instantiate it.
After this is set, we need to double check that we have set the app:navGraph attribute in the main_activity.xml layout file:
Great! Now we can navigate!
Here is a code snippet of the HomeFragment in the home module:
As you can see we can use the navController to handle the in-module navigation, and as soon as we need to switch to another module we use the navigateToFlow method from the interface.
Note: Casting the activity to ToFlowNavigatable may not be the most robust approach, consider using an EventBus to dispatch any events to the MainActivity to decouple the fragment even more. You can also inject the Navigator instance in the fragment if you are using a DI framework(just make sure it’s a singleton), or even directly use the navController and
findNavController().navigate(MainNavGraphDirections.actionGlobalDashboardFlow())
However it would be good if you use the Navigator class or MainActivity for that.
Some bonus points:
I have also included a BottomNavBar in our app and linked that to the different navigation flows. As you might have probably guessed, the menu items of the BottomNavBar just have the same android:ids as our navigation flow ids(the ones defined in the nav_ids.xml). And it works!
Great, we’ve reached the end! I hope this guide helped you and/or gave you some ideas if you were struggling with multi-module navigation. Here is a link to the repository with the sample project again:
Thanks for your time!