Jetpack Cross-NavGraph Destinations
Mar 31, 2021
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:
inflate R.navigation.navigation_login nav graph and assign it as a graph on the current navController.
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:
inflate R.navigation.navigation_login nav graph and assign it as a graph on the current navController.
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