I'm trying to figure out how to pass arguments to ...
# compose
s
I'm trying to figure out how to pass arguments to the start of my compose nav graph. I haven't worked with composable navigation before and I'm most likely doing things in an unrecommended way a bit, but here's a little more detail with what I'm trying to do. I've got a fragment with a ComposeView, and that composable content contains the composable nav graph...the simplified version is something like this:
Copy code
fun SomeComposableContent(startRoute: String) = 
    NavHost(
        route = "outer",
        startDestination = "inner/{userId}"
    ) {
        navigation(startDestination = "inner/start/{userId}", route = "inner/{userId}" {
            composable(route = "inner/start/{userId}") { backEntry ->
                val userId = backEntry.arguments?.getString("userId")
                ScreenOne(userId)
            }
        }

        composable(route = "next") {
            ScreenTwo()
        }
    }
If I use the "next" destination as my start and then navigate to the inner graph with passing an arg, things work, but if I want to start at the inner start destination, and I'm not calling navController.navigate(), how can I pass the argument? I'm basically trying to load the graph and pass the argument..is that possible?
s
ok. since this userId, or argument, isn't like a set value and changes per user, can that default value be dynamic? i guess i would split the string and grab the argument? what's the structure of the navigation string if there are multiple arguments?
i
Yes, your defaultValue can certainly be a dynamic value set by your fragment - it is just Kotlin code. Your parsing code is still all correct - any
{userId}
is still going to be automatically parsed, using the
defaultValue
by default
Routes are just uris, so
whatever/{param1}/{param2}
is perfect if they are required arguments, otherwise you'd want to use the query parameter syntax as per the docs: https://developer.android.com/jetpack/compose/navigation#optional-args
s
ok cool. it feels a little odd to parse the arg myself sometimes and letting the nav controller parse it other times. it'd be cool if they graph could figure out somehow if there was an arg. i've got some nested graphs, and the way I've been setting up the routes has been like a folder type of structure. so in my first example, the inner graph composable route would be like "outer/inner/start". do you think that's a good convention?
i
You're not parsing anything; that's done for you. You're just getting the pre-parsed value out of the Bundle.
There's no need to add extra namespaces just because; just use actual semantic names
s
You're not parsing anything; that's done for you. You're just getting the pre-parsed value out of the Bundle.
if I want to set the default value when I first load the graph, then I will have to parse that value myself, right? that part feels weird to me...i'd rather not have to parse anything and somehow have the nav controller know there's an argument.
i
You haven't shared where you're getting your default value from or how this code snippet interacts with anything else, so I couldn't tell you
s
ok yeah fair enough. I'll give you the tl;dr because I would like your feedback, plus I have another question. so I have some feature modules with their own graph and then the app module that brings the graphs together in the main graph. this is all xml/fragments. I have a new feature module that is basically all composables. there's a root fragment with the ComposeView, and the composable graph is defined in there. since everything in this module is a composable, i figured I could get away with mixing the xml and compose graphs. I may not actually need to do this anymore, but I was trying to enter into this composable graph with the args. when I get to the root fragment, I grab the args from xml nav, and construct the destination basically.
my question is that I want to add a NavHost inside another NavHost, but it doesn't seem like I can link the nav controllers. i have another flow that i want a separate graph for, and i was thinking of having a supporting composable to help with some things related to this flow. is there a way to tie the different nav hosts together somehow? for a more clear example, it looks something like this:
Copy code
NavHost() {
    composable(route = "") {
        NavHost() {
            navigation() {
                composable(route =""){
                }
            }
       }
    }
}
the idea for the inner nav host thing came from some things I've done in xml nav land...i had a root fragment with a graph that handled the state of the graph flow basically. is there a way I can have a composable with a graph that I can link to the nav host the composable is in? i'm not exactly sure how to set this new graph on the nav controller. when i use a separate navHostController for the inner nav host, i'm not sure how to nav to the other graphs in the outer nav host
i
I think the structure you've chosen is not sustainable - we have always recommended only a single host: https://developer.android.com/jetpack/compose/navigation#interoperability
Therefore, the recommendation for hybrid apps is to use the fragment-based Navigation component and use fragments to hold view-based screens, Compose screens, and screens that use both views and Compose. Once each screen fragment in your app is a wrapper around a composable, the next step is to tie all of those screens together with Navigation Compose and remove all of the fragments.
If you are in a fragment world, you should stay in a fragment world, even if your entire fragment's UI is built with Compose
s
I know it's not recommended to mix the two, but what kind of issues do you think I'll have doing the module of just composables w/ the root fragment for setting content? how is it different from just using a ComponentActivity with setContent? They're both setting the composable in a ComposeView it looks like, and having the compose nav graph in the compose views is normal. right?
b
@Ian Lake I was kinda trying to the same with nested NavHost & I'm wondering if I'm doing something not supported or doing something wrong. We have a hybrid views & compose app. All compose screens hosted in fragments. But in one of the new screens we were trying to use nested NavHostController via rememberNavController. The problem is: navigation state is not properly restored when coming back from process death (rememberNavController is resetting to initial state somehow during recompositions). Interestingly enough, when I host the compose screen directly in the activity, state restoration works:
Copy code
MyComposeFeatureScreen:
      rememberNavController()
        FeatureScreenOne
        FeatureScreenTwo

// Navigation backstack not restored
MainActivity {
  onCreate:
    jetpack fragment navigation:
      fragment:
        setContent MyComposeFeatureScreen:
}

// Navigation backstack restored
MainActivity {
  onCreate:
    setContent MyComposeFeatureScreen:
}
Diff in logs when coming back from process death:
Copy code
CustomersLaunchedEffect: navController: Destination(0x4998a1c7) route=customers/, androidx.navigation.NavBackStackEntry@198f9b8
 CustomersLaunchedEffect: customerActionResult: MutableState(value=null)@120014880
 CustomersFragment: LaunchedEffect
 CustomersLaunchedEffect: navController: Destination(0x9acde377) route=customers/{customerId}?isEditMode={isEditMode}&isInvoiceMode={isInvoiceMode}, androidx.navigation.NavBackStackEntry@73d12977
 CustomersLaunchedEffect: customerActionResult: MutableState(value=null)@50352885
 CustomersFragment: LaunchedEffect
 CustomersLaunchedEffect: navController: Destination(0x4998a1c7) route=customers/, androidx.navigation.NavBackStackEntry@e2b2011e
 CustomersLaunchedEffect: customerActionResult: MutableState(value=null)@80460568

CustomersLaunchedEffect: navController: Destination(0x4998a1c7) route=customers/, androidx.navigation.NavBackStackEntry@70ec0fac
CustomersLaunchedEffect: customerActionResult: MutableState(value=null)@101095983
So is the way we're trying to use compose NavHostController in Fragment is not right / supported? Or is it a bug in rememberNavController?
i
That's absolutely supported. There are no known cases where
rememberNavController
doesn't work but other things that rely on
rememberSaveable
do work, so that would be the first thing I'd try (just having a
rememberSaveable
integer that a button increments and seeing if that count works across process death). If neither work, then your issue is with your Fragment code
b
Hmm I think I was doing something very dumb.. I had a temporary code that was starting my MyComposeFeatureScreen on Fragment's onViewCreated.. Noticed it after trying simple ui with counter &`rememberSaveable`. The restored MyComposeFeatureScreen was behind the second newly opened MyComposeFeatureScreen.. Thanks for hint trying simple rememberSaveable!