From the documentation, I can pass string, integer etc. But how can I pass objects on navigation?
Note: If I set the argument type parcelable then the app crashes with java.lang.UnsupportedOperationException: Parcelables don't support default values.
.
composable(
"vendor/details/{vendor}",
arguments = listOf(navArgument("vendor") {
type = NavType.ParcelableType(Vendor::class.java)
})
) {
// ...
}
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.
So, if it is possible avoid passing complex data. More official details here.
Now you can pass any complex data using Kotlin Serialization officially. Here are some example code:
// Route
@Serializable
data class User(
val id: Int,
val name: String
)
// Pass data
navController.navigate(
User(id = 1, name = "John Doe")
)
// Receive Data
NavHost {
composable<User> { backStackEntry ->
val user: User = backStackEntry.toRoute()
UserDetailsScreen(user) // Here UserDetailsScreen is a composable.
}
}
// Composable view
@Composable
fun UserDetailsScreen(
user: User
){
// ...
}
For more information check out the official blog post from here.
The following workarounds are based on navigation-compose
version 2.7.5
.
I found 2 workarounds for passing objects.
Here we can pass the objects using the JSON string representation of the object.
Example code:
val ROUTE_USER_DETAILS = "user-details?user={user}"
// Pass data (I am using Moshi here)
val user = User(id = 1, name = "John Doe") // User is a data class.
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(User::class.java).lenient()
val userJson = jsonAdapter.toJson(user)
navController.navigate(
ROUTE_USER_DETAILS.replace("{user}", userJson)
)
// Receive Data
NavHost {
composable(ROUTE_USER_DETAILS) { backStackEntry ->
val userJson = backStackEntry.arguments?.getString("user")
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(User::class.java).lenient()
val userObject = jsonAdapter.fromJson(userJson)
UserDetailsScreen(userObject) // Here UserDetailsScreen is a composable.
}
}
// Composable function/view
@Composable
fun UserDetailsScreen(
user: User?
){
// ...
}
Important Note: If your data has any URL or any string with &
etc., you may have to use URLEncoder.encode(jsonString, "utf-8")
and URLDecode.decode(jsonString, "utf-8")
for pass and receive data respectively. But encoding-decoding also has some side effects! Like if your string has any +
sign, it will replace that with a space
etc.
NavBackStackEntry
Here we can pass data using navController.currentBackStackEntry
and receive data using navController.previousBackStackEntry
.
Note: From version
1.6.0
any changes to*BackStackEntry.arguments
will not be reflected in subsequent accesses to thearguments
. So, now we have to usesavedStateHandle
. Version change details here.
Example code:
val ROUTE_USER_DETAILS = "user-details"
// Pass data
val user = User(id = 1, name = "John Doe") // User is a parcelable data class.
// `arguments` will not work after version 1.6.0.
// navController.currentBackStackEntry?.arguments?.putParcelable("user", user) // old
snavController.currentBackStackEntry?.savedStateHandle?.set("user", user) // new
navController.navigate(ROUTE_USER_DETAILS)
// Receive data
NavHost {
composable(ROUTE_USER_DETAILS) {
// `arguments` will not work after version 1.6.0.
// val userObject = navController.previousBackStackEntry?.arguments?.getParcelable<User>("user") // old
val userObject: User? = navController.previousBackStackEntry?.savedStateHandle?.get("user") // new
UserDetailsScreen(userObject) // Here UserDetailsScreen is a composable.
}
}
// Composable function/view
@Composable
fun UserDetailsScreen(
user: User?
){
// ...
}
Important Note: The 2nd solution is unstable and will not work if we pop up back-stacks on navigate.