简体   繁体   中英

Jetpack Compose Navigation - pass argument to startDestination

The app I'm building uses compose navigation with routes. The challenge is that the start destination is dynamic.

Here is a minimal example:

class MainActivity : ComponentActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)

        setContent {
            val navController = rememberNavController()

            NavHost(
                navController = navController,
                startDestination = "dynamic/1", // doesn't work
                // startDestination = "static", // workaround
            ) {
                composable(
                    route = "dynamic/{$ARG_ID}",
                    arguments = listOf(navArgument(ARG_ID) { type = NavType.StringType }),
                ) {
                    val id = it.arguments?.getString(ARG_ID)
                    Text("dynamic route, received argument: $id!")
                }
                // part of the workaround
                // composable(
                //  route = "static",
                // ) {
                //  LaunchedEffect(this) {
                //      navController.navigate("dynamic/1")
                //  }
                // }
            }
        }
    }

    companion object
    {
        const val ARG_ID = "id"
    }
}

The app crashes with

java.lang.IllegalArgumentException: navigation destination route/1 is not a direct child of this NavGraph

The problem only exists if the "dynamic" route is used as start destination. This can be verified by using startDestination = "static" .

Although, the "static" route workaround works I'm looking for a solution without it because it kind of obfuscates the code and also creates an additional entry in the back stack.

-> Full code sample to reproduce the issue

Related SO questions

Edit:

I want to stress that the original sample used to not contain the "static" composable. I only added the "static" composable to have a working startDestination and to prove that the "dynamic" composable can be navigated to.

Update:

Even switching to the query parameter syntax for optional arguments , providing a default value, and setting the start destination without any argument does not work.

The following variation

NavHost(
    navController = navController,
    startDestination = "dynamic",
) {
    composable(
        route = "dynamic?$ARG_ID={$ARG_ID}",
        arguments = listOf(navArgument(ARG_ID) { type = NavType.StringType; defaultValue = "1" }),
    ) {
        val id = it.arguments?.getString(ARG_ID)
        Text("dynamic route, received argument: $id!")
    }
}

Leads to the exception

java.lang.IllegalArgumentException: navigation destination dynamic is not a direct child of this NavGraph

Full credit goes to ianhanniballake , who explained the solution to me in a comment. I'm going to show the working version of my code sample here.

The big insight to me was:

startDestination must not match a composable route in the sense of pattern matching but it must be exactly the same string .

That means an argument can't be set via startDestination directly but has to be set via the argument's defaultValue .

Here is the working sample:

class MainActivity : ComponentActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)

        setContent {
            val navController = rememberNavController()

            NavHost(
                navController = navController,
                // 1st change: Set startDestination to the exact string of route
                startDestination = "dynamic/{$ARG_ID}", // NOT "dynamic/1", provide arguments via defaultValue
            ) {
                composable(
                    route = "dynamic/{$ARG_ID}",
                    // 2nd change: Set startDestination argument via defaultValue
                    arguments = listOf(navArgument(ARG_ID) { type = NavType.StringType; defaultValue = "1" }),
                ) {
                    val id = it.arguments?.getString(ARG_ID)
                    Text("dynamic route, received argument: $id!")
                }
            }
        }
    }

    companion object
    {
        const val ARG_ID = "id"
    }
}

The approach equally works with the argument provided in the form of a query parameter.

To be honest, I see this as a small limitation because the start route now dictates what has to be the defaultValue . I might want to set a different defaultValue or none at all. Yet, in most cases this should be the most elegant solution.

should not be using dynamic route value in "startDestination" NavHost --> navController.navigate(<dynamic route >)

All credit goes to ianhanniballake and Peter . In my case I didn't add any additional (mandatory key/optional key) in route for the argument data. I kept the route clean like below:

Nav graph:

 navigation(route = Route.Root.route, startDestination = Route.SubmitForm.route) {

    composable(
        route = Route.SubmitForm.route,
        arguments = listOf(
            navArgument(ARG_KEY) {
                type = NavType.StringType
                defaultValue = JsonConverter.toJson(user, User::class.java)
            },
        )
    )
}

Route sealed class:

sealed class Route(val route: String) {
    object MyRoute : Route("$ROOT/submit-form")
}

And in view model just get the data like this:

@HiltViewModel
class MyViewModel @Inject constructor(
    stateHandle: SavedStateHandle,
) : ViewModel {

    lateinit var user

    init {
        user = stateHandle.get<String>(ARG_NAME) // Supported data types
    }
}

It worked for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM