Jetpack Cross-NavGraph Destinations

Mar 31, 2021

IMG_2070.jpg

This post describes several approaches to navigating to a destination that is in a different nav graph using Jetpack navigation library.

If you try to use a destination that’s not available in your current nav graph, your app will crash with a stacktrace like this:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: io.valueof.dynamicnavgraph, PID: 21396
    java.lang.IllegalArgumentException: 
    Navigation action/destination io.valueof.dynamicnavgraph:id/profile cannot be found from the current destination Destination(io.valueof.dynamicnavgraph:id/main) label=Main class=io.valueof.dynamicnavgraph.MainFragment

Navigating to Other Nav Graph’s Start Destination

First, why would have multiple nav graphs? to lazily inflate destinations only when needed to save time and resources. For instance, signup navigation nav graph is only useful to inflate during the onboarding flow not for every app user session.

Given navigation_main.xml

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mainNavGraph"
    app:startDestination="@+id/main">

    <fragment
        android:id="@+id/main"
        android:name="io.valueof.dynamicnavgraph.MainFragment"
        android:label="Main"
        tools:layout="@layout/fragment_main" />

</navigation>

And navigation_profile.xml

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/profileNavGraph"
    app:startDestination="@+id/profile">

    <fragment
        android:id="@+id/profile"
        android:name="io.valueof.dynamicnavgraph.ProfileFragment"
        android:label="Profile"
        tools:layout="@layout/fragment_profile" />
</navigation>

If you are in MainFragment and would like to navigate to ProfileFragment, you need to inflate R.navigation.navigation_profile nav graph and assign it as a graph on the current navController.

Nothing else to do since the ProfileFragment is a default (start) destination for the target nav graph.

val navController = findNavController()
val navGraph = navController.navInflater.inflate(R.navigation.navigation_profile)
navController.graph = navGraph

Navigating to Other Nav Graph’s Explicit Destination

Given navigation_main.xml

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mainNavGraph"
    app:startDestination="@+id/main">

    <fragment
        android:id="@+id/main"
        android:name="io.valueof.dynamicnavgraph.MainFragment"
        android:label="Main"
        tools:layout="@layout/fragment_main" />

</navigation>

And navigation_login.xml

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/loginNavGraph"
    app:startDestination="@+id/login">

    <fragment
        android:id="@+id/login"
        android:name="io.valueof.dynamicnavgraph.LoginFragment"
        android:label="Login"
        tools:layout="@layout/fragment_login" />

    <fragment
        android:id="@+id/forgotPassword"
        android:name="io.valueof.dynamicnavgraph.ForgotPasswordFragment"
        android:label="Forgot Password"
        tools:layout="@layout/fragment_forgot_password" />
</navigation>

If you are in MainFragment and would like to navigate to ForgotPasswordFragment, you need to:

  1. inflate R.navigation.navigation_login nav graph and assign it as a graph on the current navController.

  2. Set start destination for the target nav graph.

val navController = findNavController()
val navGraph = navController.navInflater
  .inflate(R.navigation.navigation_login).apply {
    startDestination = R.id.forgotPassword
}
navController.graph = navGraph

Navigating to Other Nav Graph’s Dynamic Destination

Given the same navigation_main.xml and navigation_login.xml nav graphs from previous example, if you are in MainFragment and would like to navigate to either LoginFragment or ForgotPasswordFragment conditionally, you need to:

  1. inflate R.navigation.navigation_login nav graph and assign it as a graph on the current navController.

  2. Set start destination for the target nav graph based on some condition.

val didForgetPassword = Random.nextBoolean()

val (startDestinationId, navGraphId) = if (didForgetPassword) {
  Pair(R.id.forgotPassword, R.navigation.navigation_login)
} else {
  Pair(R.id.login, R.navigation.navigation_login)
}

val navController = findNavController()
val navGraph = navController.navInflater.inflate(navGraphId).apply {
  startDestination = startDestinationId
}
navController.graph = navGraph

Alternatively, if both destinations share navigation_login.xml nav graph:

val didForgetPassword = Random.nextBoolean()

val (startDestinationId) = if (didForgetPassword) {
  R.id.forgotPassword
} else {
  R.id.login
}

val navController = findNavController()
val navGraph = navController.navInflater.inflate(R.navigation.navigation_login).apply {
  startDestination = startDestinationId
}
navController.graph = navGraph

As always, you can find working source code for these examples at https://github.com/jshvarts/DynamicNavGraphDemo

 
Previous
Previous

Retrying Network Requests with Flow

Next
Next

ConstraintLayout Animation 101