Reputation: 3310
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")
}
Upvotes: 19
Views: 23837
Reputation: 2038
you can pass an argument like this
val data = DestinationScreenArgument(title = "Hello")
navController.currentBackStackEntry?.savedStateHandle?.apply {
set("detailArgument", data)
}
navController.navigate(Screen.DetailScreen.route)
and get the argument in the destination like this
val detailArgument = navController.previousBackStackEntry?.savedStateHandle?.get<DestinationScreenArgument>(
"detailArgument"
)
Upvotes: 12
Reputation: 452
A very simple and basic way to do is as below
1.First create the parcelable object that you want to pass e.g
@Parcelize
data class User(
val name: String,
val phoneNumber:String
) : Parcelable
2.Then in the current composable that you are in e.g main screen
val userDetails = UserDetails(
name = "emma",
phoneNumber = "1234"
)
)
navController.currentBackStackEntry?.arguments?.apply {
putParcelable("userDetails",userDetails)
}
navController.navigate(Destination.DetailsScreen.route)
3.Then in the details composable, make sure you pass to it a navcontroller as an parameter e.g.
@Composable
fun Details (navController:NavController){
val data = remember {
mutableStateOf(navController.previousBackStackEntry?.arguments?.getParcelable<UserDetails>("userDetails")!!)
}
}
N.B: If the parcelable is not passed into state, you will receive an error when navigating back
Upvotes: 5
Reputation: 199880
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.
Upvotes: 35