23

I'm trying to implement a multiple navigation controller with multiple back stack BottomNavigationView, as per the github examples. However, the example uses a different nav graph for each tab, which makes things easy. In my case I need to use the same nav graph for all of my tabs, but with different start destinations, than the "home destination" set in the nav graph.

So far I've managed to modify the NavigationExtensions file in order to achieve the single navgraph for all tabs, and I get multiple navControllers with their own back stacks, but I cannot figure out how to start a nav graph at a different destination.

I've tried using .navigate when getting the navcontroller, but since it's not yet attached, it does not work. Any ideas on how to achieve this? Thank you.

Jonathan Willcock
  • 5,012
  • 3
  • 20
  • 31
Lucas P.
  • 4,282
  • 4
  • 29
  • 50

2 Answers2

61

We had a requirement wherein the displayed start screen would depend if the user has logged on, what we did was the following:

  1. Remove app:startDestination on the navigation XML if it exists
  2. On your main activity's XML, remove the following fields inside the <fragment> tag: app:defaultNavHost="true" and app:navGraph
  3. On our main Activity's onCreate(), we create a NavGraph object :

    NavController navController = Navigation.findNavController(this, R.id.your_nav_hostfragment);    
    NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.your_navigation_xml);
    
  4. Then depending on your requirement, you can set the start destination on the NavGraph using setStartDestination, then apply the NavGraph to the NavController:

        if (condition) {
            navGraph.setStartDestination(R.id.screen1);
        } else {
            navGraph.setStartDestination(R.id.screen2);
        }
        navController.setGraph(navGraph);
    
Alvin Dizon
  • 1,855
  • 14
  • 34
  • Thank you very much, this brought me one step closer to solving my problem. Is there a way to pass arguments to the fragment passed as the start destination? I have a fragment that loads different data when different ids are passed. – Lucas P. Jul 02 '19 at 08:01
  • You can try the Safe Args plugin of the Navigation Component, see this link: https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args – Alvin Dizon Jul 02 '19 at 11:18
  • 1
    I am already using safe args, but the directions create an action, that should be passed to a NavController with `.navigate(action)`, which cannot be passed as a startDestination to the navGraph. Any diead? – Lucas P. Jul 02 '19 at 11:29
  • In that case, then I am not aware of any Navigation Component-related solution to your requirement. Instead of feeding the data as Fragment arguments, I think it might be better that your Fragments fetch the needed data from somewhere else (like a Repository class). – Alvin Dizon Jul 03 '19 at 01:37
  • Thanks @akubi. My Fragments actually do that, I only feed them a string ID so the fragment knows what to draw from the underlying DB/repository/cache. – Lucas P. Jul 04 '19 at 09:59
  • 1
    @AhsanSaeed I had the same problem. Leaving `app:defaultNavHost="true"` in `androidx.fragment.app.FragmentContainerView` fixes it. Android reference [says](https://developer.android.com/guide/navigation/navigation-getting-started): "The `app:defaultNavHost="true"` attribute ensures that your `NavHostFragment` intercepts the system Back button. Note that only one `NavHost` can be the default. If you have multiple hosts in the same layout (two-pane layouts, for example), be sure to specify only one default `NavHost`." – t3ddys Mar 08 '21 at 18:24
  • In my case, when I override the navGraph within the host activity, the fragment that is the start destination was giving error and crashing the app where I use requireContext() reporting; "Fragment is not attached to any context". This answer solved my issue, a big thanks to him/her. This is really weird in the official documentation there is not any description or note that states this issue. @akubi where did you gather this information from? – Kozmotronik Jul 13 '21 at 11:52
  • 2
    However, in my case it still worked even though I kept using `app:startDestination` and `app:defaultNavHost="true"`. I think the main issue for my case was setting navGraph both from within xml resource and runtime. – Kozmotronik Jul 13 '21 at 12:02
  • @Kozmotronik when I wrote the answer my intention was to set the nav graph start destination in code rather than use XML – Alvin Dizon Jul 18 '21 at 00:46
  • If I don't remove app:defaultNavHost="true" then it works like a charm. If i removed defaultNavHost then back navigation not worked properly. – Tayyab Amin Sep 16 '22 at 05:52
9

I wrote two extensions to do the job. First is to be used to change destination in the current node. The second is to be used to traverse multiple times into nested graphs

_main_graph
 |
 |_nested_graph_1
   |
   |_nested_graph_2  
     |
     |_nested_graph_3
       |
       |_nested_graph_4
         |
         |_change this destination!!!!  
fun NavController.changeNodeDestination(nodeId: Int, destinationId: Int): NavController {
    val graph = graph.findNode(nodeId) as NavGraph
    graph.startDestination = destinationId
    return this
}

fun NavController.changeNodeDestination(vararg nodeIds: Int, destinationId: Int): NavController {
    var currentNode = graph

    nodeIds.forEachIndexed { index, i ->
        currentNode = currentNode.findNode(nodeIds[index]) as NavGraph
    }
    currentNode.startDestination = destinationId
    return this
}

Usage:

        findNavController().changeNodeDestination(
            R.id.navigation_login,
            R.id.nav_change_password,                  // GO two levels inside nested graphs
            destinationId = startDestination
        ).navigate(navID, bundle)
Morteza Rastgoo
  • 6,772
  • 7
  • 40
  • 61