Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
660 views
in Technique[技术] by (71.8m points)

android - How pass parcelable argument with new version of compose navigation?

I had an app made with jetpack compose that worked fine until I upgraded the compose navigation library from version 2.4.0-alpha07 to version 2.4.0-alpha08 In the alpha08 version it seems to me that the arguments attribute of the NavBackStackEntry class is a val, so it can't be reassigned as we did in the 2.4.0-alpha07 version. How to solve this problem in version 2.4.0-alpha08?

My navigation component is this:

@Composable
private fun NavigationComponent(navController: NavHostController) {
    NavHost(navController = navController, startDestination = "home") {
        composable("home") { HomeScreen(navController) }
        composable("details") {
            val planet = navController
                .previousBackStackEntry
                ?.arguments
                ?.getParcelable<Planet>("planet")
            planet?.let {
                DetailsScreen(it, navController)
            }
        }
    }
}

The part where I try to make the navigation happen to the details page is in this function:

private fun navigateToPlanet(navController: NavHostController, planet: Planet) {
    navController.currentBackStackEntry?.arguments = Bundle().apply {
        putParcelable("planet", planet)
    }
    navController.navigate("details")
}

I've already tried simply applying to the recurring arguments of the navigateToPlanet function using apply but it doesn't work, the screen opens blank without any information. This is the code for my failed attempt:

private fun navigateToPlanet(navController: NavHostController, planet: Planet) {
    navController.currentBackStackEntry?.arguments?.apply {
        putParcelable("planet", planet)
    }
    navController.navigate("details")
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

As per the Navigation documentation:

Caution: Passing complex data structures over arguments is considered an anti-pattern. Each destination should be responsible for loading UI data based on the minimum necessary information, such as item IDs. This simplifies process recreation and avoids potential data inconsistencies.

You shouldn't be passing Parcelables at all as arguments and never has been a recommended pattern: not in Navigation 2.4.0-alpha07 nor in Navigation 2.4.0-alpha08. Instead, you should be reading data from a single source of truth. In your case, this is your Planet.data static array, but would normally be a repository layer, responsible for loading data for your app.

This means what you should be passing through to your DetailsScreen is not a Planet itself, but the unique key that defines how to retrieve that Planet object. In your simple case, this might just be the index of the selected Planet.

By following the guide for navigating with arguments, this means your graph would look like:

@Composable
private fun NavigationComponent(navController: NavHostController) {
    NavHost(navController = navController, startDestination = HOME) {
        composable(HOME) { HomeScreen(navController) }
        composable(
            "$DETAILS/{index}",
            arguments = listOf(navArgument("index") { type = NavType.IntType }
        ) { backStackEntry ->
            val index = backStackEntry.arguments?.getInt("index") ?: 0
            // Read from our single source of truth
            // This means if that data later becomes *not* static, you'll
            // be able to easily substitute this out for an observable
            // data source
            val planet = Planet.data[index]
            DetailsScreen(planet, navController)
        }
    }
}

As per the Testing guide for Navigation Compose, you shouldn't be passing your NavController down through your hierarchy - this code cannot be easily tested and you can't use @Preview to preview your composables. Instead, you should:

  • Pass only parsed arguments into your composable
  • Pass lambdas that should be triggered by the composable to navigate, rather than the NavController itself.

So you shouldn't be passing your NavController down to HomeScreen or DetailsScreen at all. You might start this effort to make your code more testable by first changing your usage of it in your PlanetCard, which should take a lambda, instead of a NavController:

@Composable
private fun PlanetCard(planet: Planet, onClick: () -> Unit) {
    Card(
        elevation = 4.dp,
        shape = RoundedCornerShape(15.dp),
        border = BorderStroke(
            width = 2.dp,
            color = Color(0x77f5f5f5),
        ),
        modifier = Modifier
            .fillMaxWidth()
            .padding(5.dp)
            .height(120.dp)
            .clickable { onClick() }
    ) {
       ...
    }
}

This means your PlanetList can be written as:

@Composable
private fun PlanetList(navController: NavHostController) {
    LazyColumn {
        itemsIndexed(Planet.data) { index, planet ->
            PlanetCard(planet) {
                // Here we pass the index of the selected item as an argument
                navController.navigate("${MainActivity.DETAILS}/$index")
            }
        }
    }
}

You can see how continuing to use lambdas up the hierarchy would help encapsulate your MainActivity constants in that class alone, instead of spreading them across your code base.

By switching to using an index, you've avoiding creating a second source of truth (your arguments themselves) and instead set yourself up to write testable code that will support further expansion beyond a static set of data.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...