title: Android Advanced Navigation deeplinking between graphs
body: <p>I have been using <a href="https://github.com/android/architecture-components-samples/tree/main/NavigationAdvancedSample" rel="nofollow noreferrer">Android Advanced Navigation</a> for a while. In my current project, I have three navigation graphs. The problem is, some of the fragments in one graph should be reached from another graph. In order to solve this, I've made deep links.</p>
  <p>For example, in graph A I've included graph B and then used a deep link from graph B to reach that particular fragment. The problem is when I am in graph B and now I want to jump back to graph A I can't. As graph A is not included in graph B, the current navigation controller can't find the destination. If I include graph A in graph B, another problem occurs. Android Studio can't build the project as it is having a circular import problem (graph B tries to import graph A but graph B is already included in graph A etc.) and I really don't know what else to do.</p>
  <p>I've tried creating one huge navigation graph which contains all three subgraphs but I couldn't make it work with this Android Advanced Navigation. Is there any more efficient way?</p>
  <p>Edit to add code:</p>
  <p>BottomNavigationView extension:</p>
  <pre><code>fun BottomNavigationView.setupWithNavController(
      navGraphIds: List&lt;Int&gt;,
      fragmentManager: FragmentManager,
      containerId: Int,
      intent: Intent
  ): LiveData&lt;NavController&gt; {
  
      // Map of tags
      val graphIdToTagMap = SparseArray&lt;String&gt;()
      // Result. Mutable live data with the selected controlled
      val selectedNavController = MutableLiveData&lt;NavController&gt;()
  
      var firstFragmentGraphId = 0
  
      // First create a NavHostFragment for each NavGraph ID
      navGraphIds.forEachIndexed { index, navGraphId -&gt;
          val fragmentTag = getFragmentTag(index)
  
          // Find or create the Navigation host fragment
          val navHostFragment = obtainNavHostFragment(
              fragmentManager,
              fragmentTag,
              navGraphId,
              containerId
          )
  
          // Obtain its id
          val graphId = navHostFragment.navController.graph.id
  
          if (index == 0) {
              firstFragmentGraphId = graphId
          }
  
          // Save to the map
          graphIdToTagMap[graphId] = fragmentTag
  
          // Attach or detach nav host fragment depending on whether it's the selected item.
          if (this.selectedItemId == graphId) {
              // Update livedata with the selected graph
              selectedNavController.value = navHostFragment.navController
              attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
          } else {
              detachNavHostFragment(fragmentManager, navHostFragment)
          }
      }
  
      // Now connect selecting an item with swapping Fragments
      var selectedItemTag = graphIdToTagMap[this.selectedItemId]
      val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
      var isOnFirstFragment = selectedItemTag == firstFragmentTag
  
      // When a navigation item is selected
      setOnNavigationItemSelectedListener { item -&gt;
          // Don't do anything if the state is state has already been saved.
          if (fragmentManager.isStateSaved) {
              false
          } else {
              val newlySelectedItemTag = graphIdToTagMap[item.itemId]
              if (selectedItemTag != newlySelectedItemTag) {
                  // Pop everything above the first fragment (the &quot;fixed start destination&quot;)
                  fragmentManager.popBackStack(firstFragmentTag,
                      FragmentManager.POP_BACK_STACK_INCLUSIVE)
                  val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
                          as NavHostFragment
  
                  // Exclude the first fragment tag because it's always in the back stack.
                  if (firstFragmentTag != newlySelectedItemTag) {
                      // Commit a transaction that cleans the back stack and adds the first fragment
                      // to it, creating the fixed started destination.
                      fragmentManager.beginTransaction()
                          .setCustomAnimations(
                              R.anim.nav_default_enter_anim,
                              R.anim.nav_default_exit_anim,
                              R.anim.nav_default_pop_enter_anim,
                              R.anim.nav_default_pop_exit_anim)
                          .attach(selectedFragment)
                          .setPrimaryNavigationFragment(selectedFragment)
                          .apply {
                              // Detach all other Fragments
                              graphIdToTagMap.forEach { _, fragmentTagIter -&gt;
                                  if (fragmentTagIter != newlySelectedItemTag) {
                                      detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
                                  }
                              }
                          }
                          .addToBackStack(firstFragmentTag)
                          .setReorderingAllowed(true)
                          .commit()
                  }
                  selectedItemTag = newlySelectedItemTag
                  isOnFirstFragment = selectedItemTag == firstFragmentTag
                  selectedNavController.value = selectedFragment.navController
                  true
              } else {
                  false
              }
          }
      }
  
      // Optional: on item reselected, pop back stack to the destination of the graph
      setupItemReselected(graphIdToTagMap, fragmentManager)
  
      // Handle deep link
      setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
  
      // Finally, ensure that we update our BottomNavigationView when the back stack changes
      fragmentManager.addOnBackStackChangedListener {
          if (!isOnFirstFragment &amp;&amp; !fragmentManager.isOnBackStack(firstFragmentTag)) {
              this.selectedItemId = firstFragmentGraphId
          }
  
          // Reset the graph if the currentDestination is not valid (happens when the back
          // stack is popped after using the back button).
          selectedNavController.value?.let { controller -&gt;
              if (controller.currentDestination == null) {
                  controller.navigate(controller.graph.id)
              }
          }
      }
      return selectedNavController
  }
  
  private fun BottomNavigationView.setupDeepLinks(
      navGraphIds: List&lt;Int&gt;,
      fragmentManager: FragmentManager,
      containerId: Int,
      intent: Intent
  ) {
      navGraphIds.forEachIndexed { index, navGraphId -&gt;
          val fragmentTag = getFragmentTag(index)
  
          // Find or create the Navigation host fragment
          val navHostFragment = obtainNavHostFragment(
              fragmentManager,
              fragmentTag,
              navGraphId,
              containerId
          )
          // Handle Intent
          if (navHostFragment.navController.handleDeepLink(intent)
              &amp;&amp; selectedItemId != navHostFragment.navController.graph.id) {
              this.selectedItemId = navHostFragment.navController.graph.id
          }
      }
  }
  
  private fun BottomNavigationView.setupItemReselected(
      graphIdToTagMap: SparseArray&lt;String&gt;,
      fragmentManager: FragmentManager
  ) {
      setOnNavigationItemReselectedListener { item -&gt;
          val newlySelectedItemTag = graphIdToTagMap[item.itemId]
          val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
                  as NavHostFragment
          val navController = selectedFragment.navController
          // Pop the back stack to the start destination of the current navController graph
          navController.popBackStack(
              navController.graph.startDestination, false
          )
      }
  }
  
  private fun detachNavHostFragment(
      fragmentManager: FragmentManager,
      navHostFragment: NavHostFragment
  ) {
      fragmentManager.beginTransaction()
          .detach(navHostFragment)
          .commitNow()
  }
  
  private fun attachNavHostFragment(
      fragmentManager: FragmentManager,
      navHostFragment: NavHostFragment,
      isPrimaryNavFragment: Boolean
  ) {
      fragmentManager.beginTransaction()
          .attach(navHostFragment)
          .apply {
              if (isPrimaryNavFragment) {
                  setPrimaryNavigationFragment(navHostFragment)
              }
          }
          .commitNow()
  
  }
  
  private fun obtainNavHostFragment(
      fragmentManager: FragmentManager,
      fragmentTag: String,
      navGraphId: Int,
      containerId: Int
  ): NavHostFragment {
      // If the Nav Host fragment exists, return it
      val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
      existingFragment?.let { return it }
  
      // Otherwise, create it and return it.
      val navHostFragment = NavHostFragment.create(navGraphId)
      fragmentManager.beginTransaction()
          .add(containerId, navHostFragment, fragmentTag)
          .commitNow()
      return navHostFragment
  }
  
  private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
      val backStackCount = backStackEntryCount
      for (index in 0 until backStackCount) {
          if (getBackStackEntryAt(index).name == backStackName) {
              return true
          }
      }
      return false
  }
  
  private fun getFragmentTag(index: Int) = &quot;bottomNavigation#$index&quot;
  </code></pre>
  <p>Navigation graphs:</p>
  <pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
  &lt;navigation xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
      xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
      xmlns:tools=&quot;http://schemas.android.com/tools&quot;
      android:id=&quot;@+id/home_nav_graph&quot;
      app:startDestination=&quot;@id/homeFragment&quot;&gt;
  
      &lt;fragment
          android:id=&quot;@+id/homeFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.home.HomeFragment&quot;
          android:label=&quot;home_fragment&quot;
          tools:layout=&quot;@layout/home_fragment&quot; &gt;
          &lt;action
              android:id=&quot;@+id/homeToGameResults&quot;
              app:destination=&quot;@id/gameResultsFragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/homeToPlayLottoGames&quot;
              app:destination=&quot;@id/playLottoGamesFragment&quot; /&gt;
      &lt;/fragment&gt;
      &lt;fragment
          android:id=&quot;@+id/checkoutFragment&quot;
          tools:layout=&quot;@layout/checkout_fragment&quot;
          android:name=&quot;lu.thebiggame.fragments.checkout.CheckoutFragment&quot;
          android:label=&quot;CheckoutFragment&quot; /&gt;
      &lt;fragment
          android:id=&quot;@+id/gameResultsFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.gameresults.GameResultsFragment&quot;
          android:label=&quot;game_results_fragment&quot;
          tools:layout=&quot;@layout/game_results_fragment&quot;&gt;
          &lt;deepLink
              android:id=&quot;@+id/gameResultsFragmentDeepLink&quot;
              app:uri=&quot;app://home/game-results-fragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/gameResultsToPlayLottoGames&quot;
              app:destination=&quot;@id/playLottoGamesFragment&quot; /&gt;
      &lt;/fragment&gt;
      &lt;fragment
          android:id=&quot;@+id/congratulationsFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.congratulations.CongratulationsFragment&quot;
          android:label=&quot;congratulations_fragment&quot;
          tools:layout=&quot;@layout/congratulations_fragment&quot; &gt;
          &lt;action
              android:id=&quot;@+id/congratulationsToClaim&quot;
              app:destination=&quot;@id/claimFragment&quot; /&gt;
          &lt;deepLink
              android:id=&quot;@+id/congratulationsFragmentDeepLink&quot;
              app:uri=&quot;app://home/congratulations-fragment&quot; /&gt;
      &lt;/fragment&gt;
      &lt;fragment
          android:id=&quot;@+id/claimFragment&quot;
          tools:layout=&quot;@layout/claim_fragment&quot;
          android:name=&quot;lu.thebiggame.fragments.claim.ClaimFragment&quot;
          android:label=&quot;ClaimFragment&quot; /&gt;
  
      &lt;include app:graph=&quot;@navigation/profile_nav_graph&quot;/&gt;
      &lt;fragment
          android:id=&quot;@+id/aboutGameFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.page.PageFragment&quot;
          android:label=&quot;page_fragment&quot;
          tools:layout=&quot;@layout/page_fragment&quot;&gt;
  
          &lt;deepLink
              android:id=&quot;@+id/aboutGameFragmentDeepLink&quot;
              app:uri=&quot;app://home/about-game-fragment&quot; /&gt;
      &lt;/fragment&gt;
      &lt;fragment
          android:id=&quot;@+id/playLottoGamesFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.play.lottogames.PlayLottoGamesFragment&quot;
          tools:layout=&quot;@layout/play_lotto_games_fragment&quot;
          android:label=&quot;PlayLottoGamesFragment&quot; &gt;
          &lt;action
              android:id=&quot;@+id/playLottoGamesToCheckout&quot;
              app:destination=&quot;@id/checkoutFragment&quot; /&gt;
          &lt;deepLink
              android:id=&quot;@+id/playLottoGamesDeepLink&quot;
              app:uri=&quot;app://home/play-lotto-games-fragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/playLottoGamesToGameResults&quot;
              app:destination=&quot;@id/gameResultsFragment&quot; /&gt;
      &lt;/fragment&gt;
  
  &lt;/navigation&gt;
  </code></pre>
  <pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
  &lt;navigation xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
      xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
      xmlns:tools=&quot;http://schemas.android.com/tools&quot;
      android:id=&quot;@+id/games_nav_graph&quot;
      app:startDestination=&quot;@id/gamesFragment&quot;&gt;
  
      &lt;fragment
          android:id=&quot;@+id/gamesFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.games.GamesFragment&quot;
          android:label=&quot;games_fragment&quot;
          tools:layout=&quot;@layout/games_fragment&quot; /&gt;
  
      &lt;include app:graph=&quot;@navigation/home_nav_graph&quot;/&gt;
  &lt;/navigation&gt;
  </code></pre>
  <pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
  &lt;navigation xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
      xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
      xmlns:tools=&quot;http://schemas.android.com/tools&quot;
      android:id=&quot;@+id/profile_nav_graph&quot;
      app:startDestination=&quot;@id/profileFragment&quot;&gt;
  
      &lt;fragment
          android:id=&quot;@+id/profileFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.profile.ProfileFragment&quot;
          android:label=&quot;profile_fragment&quot;
          tools:layout=&quot;@layout/profile_fragment&quot; &gt;
          &lt;action
              android:id=&quot;@+id/profileToResults&quot;
              app:destination=&quot;@id/resultsFragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/profileToHelp&quot;
              app:destination=&quot;@id/helpFragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/profileToPersonal&quot;
              app:destination=&quot;@id/personalFragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/profileToChangePassword&quot;
              app:destination=&quot;@id/changePasswordFragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/profileToPage&quot;
              app:destination=&quot;@id/pageFragment&quot; /&gt;
      &lt;/fragment&gt;
      &lt;fragment
          android:id=&quot;@+id/resultsFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.results.ResultsFragment&quot;
          android:label=&quot;results_fragment&quot;
          tools:layout=&quot;@layout/results_fragment&quot; &gt;
          &lt;argument
              android:name=&quot;startingTab&quot;
              app:argType=&quot;integer&quot;
              android:defaultValue=&quot;0&quot; /&gt;
          &lt;deepLink
              android:id=&quot;@+id/resultsFragmentDeepLink&quot;
              android:autoVerify=&quot;true&quot;
              app:uri=&quot;app://profile/results-fragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/resultsToClaim&quot;
              app:destination=&quot;@id/claimFragmentResults&quot; /&gt;
      &lt;/fragment&gt;
      &lt;fragment
          android:id=&quot;@+id/helpFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.help.HelpFragment&quot;
          android:label=&quot;help_fragment&quot;
          tools:layout=&quot;@layout/help_fragment&quot; &gt;
          &lt;action
              android:id=&quot;@+id/helpToPage&quot;
              app:destination=&quot;@id/pageFragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/helpToContact&quot;
              app:destination=&quot;@id/contactUsFragment&quot; /&gt;
          &lt;action
              android:id=&quot;@+id/helpToFaq&quot;
              app:destination=&quot;@id/faqFragment&quot; /&gt;
      &lt;/fragment&gt;
      &lt;fragment
          android:id=&quot;@+id/personalFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.personal.PersonalFragment&quot;
          android:label=&quot;personal_fragment&quot;
          tools:layout=&quot;@layout/personal_fragment&quot; /&gt;
      &lt;fragment
          android:id=&quot;@+id/changePasswordFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.changepassword.ChangePasswordFragment&quot;
          android:label=&quot;change_password_fragment&quot;
          tools:layout=&quot;@layout/change_password_fragment&quot; /&gt;
      &lt;fragment
          android:id=&quot;@+id/pageFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.page.PageFragment&quot;
          android:label=&quot;page_fragment&quot;
          tools:layout=&quot;@layout/page_fragment&quot; /&gt;
      &lt;fragment
          android:id=&quot;@+id/contactUsFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.contactus.ContactUsFragment&quot;
          android:label=&quot;contact_us_fragment&quot;
          tools:layout=&quot;@layout/contact_us_fragment&quot; /&gt;
      &lt;fragment
          android:id=&quot;@+id/faqFragment&quot;
          android:name=&quot;lu.thebiggame.fragments.faqfragment.FaqFragment&quot;
          android:label=&quot;faq_fragment&quot;
          tools:layout=&quot;@layout/faq_fragment&quot; /&gt;
      &lt;fragment
          android:id=&quot;@+id/claimFragmentResults&quot;
          tools:layout=&quot;@layout/claim_fragment&quot;
          android:name=&quot;lu.thebiggame.fragments.claim.ClaimFragment&quot;
          android:label=&quot;ClaimFragment&quot; /&gt;
  
  
  &lt;/navigation&gt;
  </code></pre>
  <p>Initializing bottom navigation:</p>
  <pre><code>    private fun setupBottomNavigationBar() {
  
          activityMainBottomNav.itemIconTintList = null
  
          val navGraphIds = listOf(
              R.navigation.home_nav_graph,
              R.navigation.games_nav_graph,
              R.navigation.profile_nav_graph
          )
  
          val controller = activityMainBottomNav.setupWithNavController(
              navGraphIds = navGraphIds,
              fragmentManager = supportFragmentManager,
              containerId = R.id.nav_host_container,
              intent = intent
          )
  
          currentNavController = controller
      }
  </code></pre>