The navigation component for Android allows managing navigation inside your app easier than what was done earlier in Android where we used to have an Activity
per screen.
The navigation component assumes the Single Activity Pattern is being implemented, so each screen in the app, is now a Fragment
.
It comes with a graphic tool that gives the developer a birds-eye view of how the navigation logic is defined, and facilitates adding new Fragments
and actions to navigate without using code (there is also the option to use code if you want).
For smaller or short-life apps, keeping a single navigation graph is a good idea, and the best option. Nevertheless, if you are planning on building something bigger, a multimodule project is the way to go.
If you are new to multimodule projects, I recommend that you read this official guide before reading this article.
In this article we are going to learn how to implement navigation using a graph per module, how to connect them with each other, while also adding conditional navigation, for example, for a login screen.
How to structure the navigation graphs
For the multimodule project, we are going to follow the next modules.

The idea is to have a parent navigation graph that acts as a container, with smaller, contained navigation graphs for each module.
The app
module is going to be the responsible to create the parent navigation graph and to decide the starting point of the app, as we will see in the following section when we add conditional navigation.
For now, let’s imagine that we have always the same starting point of our app, inside a feature module called home.
First, we need to create our parent navigation graph, that will be stored in the app
module, folder res/navigation
.
As the starting destination is fixed for now, we can set it through the app:startDestination
property.
The parent navigation graph is going to contain three navigation graphs, one per feature module, as stated before. In the other modules, we created other navigation graphs, with just a simple Fragment
.
<?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"
app:startDestination="@id/home_nav_graph" >
<include
android:id="@+id/homeNav"
app:graph="@navigation/home_nav_graph" />
<include
android:id="@+id/adminNav"
app:graph="@navigation/admin_nav_graph" />
<include
android:id="@+id/playerNav"
app:graph="@navigation/player_nav_graph" />
</navigation>
XMLIn the XML that inflates the only Activity
that the app is going to use, we need a FragmentContainerView
like so.
<androidx.fragment.app.FragmentContainerView
android:id="@+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
XMLTo finish the initial setup, we need now to go to the Activity
class and set the navController
.
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.navHostFragment) as? NavHostFragment
navHostFragment?.apply {
this@MainActivity.navController = navController
}
KotlinWith just this code, we can start the app, and start using the navigation implemented in the feature home module.
Navigate between modules
To navigate between two modules, we need a deeplink. Every child navigation graph is isolated from each other, so we don’t have available the actions to navigate between the fragments of different navigation graphs.
To use a deeplink we need to create an identifier for it, which you can store in the core module, as at least two modules are going to use it. Here I’m also adding an argument, an identifier as a string, for example.
If you don’t need to pass arguments, just don’t add that last part between brackets.
<string name="admin_deep_link" translatable="false">myProject://com.molidev8.myProject/admin/{adminId}</string>
XMLThen, in the child navigation graph where we are going to navigate, we need to at the deeplink to the concrete Fragment
like so, with an argument that matches the one we specified before in the URL of the deeplink.
<fragment
android:id="@+id/admin_fragment"
android:name="presentation.admin.AdminFragment"
android:label="AdminFragment"
tools:layout="@layout/admin_fragment">
<argument
android:name="adminId"
app:argType="string" />
<deepLink app:uri="@string/admin_deep_link" />
</fragment>
XMLNow, to use the deeplink and navigate, we need the following code, where we replace the placeholder for the admin identifier with an actual value.
val deeplink =
NavDeepLinkRequest.Builder.fromUri(
Uri.parse(
getString(R.string.admin_deep_link).replace(
"{adminId}",
"123456abc"
)
)
)
.build()
findNavController().navigate(deeplink)
KotlinIn the Fragment
that we are navigating to, we can read the argument using Safe Args.
I suggest that you implement each deeplink navigation action as an extension function of the NavController
if you plan on using them more than once.
fun NavController.navigateToAdmin(adminId: String) {
val deeplink =
NavDeepLinkRequest.Builder.fromUri(
Uri.parse(
getString(R.string.player_deep_link).replace(
"{adminId}",
adminId
)
)
)
.build()
findNavController().navigate(deeplink)
}
// Then call
findNavController().navigateToAdmin("1234556abc")
KotlinAdding conditional navigation
Now, let’s suppose that the home module responsibility is to manage authentication. In this case, the app is going to load the home module if a user has never logged before, or the admin or player module in case he has.
To start, we need to remove the app:startDestination
property from the parent navigation graph, as now we need it to be dynamic.
Where we created before the NavController
in our Activity
, we now need to also set the navigation graph that is going to be the entry point of the app, depending on the user authentication state.
For that, I created a sealed class for each authentication state, with the destination for each state already set. For each destination, a reference to each graph is needed, like in the following example.
sealed class StartDestination(val destination: Int) {
object Login : StartDestination(com.molidev8.feature_home.R.id.home_nav_graph)
object Admin : StartDestination(com.molidev8.feature_admin.R.id.admin_nav_graph)
object Player : StartDestination(com.molidev8.feature_player.R.id.player_nav_graph)
}
KotlinAnd finally, we set the graph like this, where the startDestination
variable is one of the objects from the sealed class we created before.
navController.graph = navController.navInflater.inflate(R.navigation.app_nav_graph).apply {
setStartDestination(startDestination.destination)
}
KotlinConclusion
Now you have a navigation graph per module, and it’s easier to see how the navigation works inside the app. This way, new developers can understand navigation easier, and you keep everything cohesion high and modules decoupled.
Featured image by AbsolutVision on Unsplash
If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.