简体   繁体   English

Jetpack Compose Navigation - 将参数传递给 startDestination

[英]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" .这可以通过使用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.我只添加了“静态”可组合项以获得有效的startDestination并证明可以导航到“动态”可组合项。

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.即使切换到可选 arguments 的查询参数语法,提供默认值,并设置没有任何参数的起始目的地也不起作用。

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.完全归功于ianhanniballake ,他在评论中向我解释了解决方案。 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 . startDestination不能匹配模式匹配意义上的可组合route ,但它必须是完全相同的字符串

That means an argument can't be set via startDestination directly but has to be set via the argument's defaultValue .这意味着参数不能直接通过startDestination设置,而必须通过参数的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 .老实说,我认为这是一个小限制,因为现在开始路线决定了defaultValue必须是什么。 I might want to set a different defaultValue or none at all.我可能想设置一个不同的defaultValue或根本没有。 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 >)不应在“startDestination”NavHost 中使用动态路由值 --> navController.navigate(<dynamic route >)

All credit goes to ianhanniballake and Peter .所有功劳归功于ianhanniballakePeter 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:路由封class:

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

And in view model just get the data like this:在视图 model 中,只需获取如下数据:

@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.它对我有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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