如何在 Jetpack Compose 的导航中传递对象?
How to pass object in navigation in jetpack compose?
从 documentation 开始,我可以传递字符串、整数等。但是如何在导航中传递对象?
注意:如果我将参数类型设置为 parcelable,则应用会崩溃并显示 java.lang.UnsupportedOperationException: Parcelables don't support default values.
。
composable(
"vendor/details/{vendor}",
arguments = listOf(navArgument("vendor") {
type = NavType.ParcelableType(Vendor::class.java)
})
) {
// ...
}
以下解决方法基于 navigation-compose
版本 2.4.0-alpha05
。
我找到了 2 个传递对象的解决方法。
1.将对象转换成JSON字符串:
这里我们可以使用对象的 JSON 字符串表示来传递对象。
示例代码:
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)
UserDetailsView(userObject) // Here UserDetailsView is a composable.
}
}
// Composable function/view
@Composable
fun UserDetailsView(
user: User
){
// ...
}
2。使用 NavBackStackEntry
:
传递对象
这里我们可以使用navController.currentBackStackEntry
传递数据,使用navController.previousBackStackEntry
接收数据。
示例代码:
val ROUTE_USER_DETAILS = "user-details/{user}"
// Pass data
val user = User(id = 1, name = "John Doe") // User is a parcelable data class.
navController.currentBackStackEntry?.arguments?.putParcelable("user", user)
navController.navigate(ROUTE_USER_DETAILS)
// Receive data
NavHost {
composable(ROUTE_USER_DETAILS) { backStackEntry ->
val userObject = navController.previousBackStackEntry?.arguments?.getParcelable<User>("user")
UserDetailsView(userObject) // Here UserDetailsView is a composable.
}
}
// Composable function/view
@Composable
fun UserDetailsView(
user: User
){
// ...
}
重要说明:如果我们在导航时弹出返回堆栈,第二种解决方案将不起作用。
Parcelables 目前不支持默认值,因此您需要将对象作为字符串值传递。是的,这是一种解决方法。因此,我们可以将该对象转换为 JSON(字符串)并通过导航传递,然后将 JSON 解析回目的地的对象,而不是将对象本身作为 Parcelize 对象传递.您可以使用 GSON 进行对象到 json 字符串的转换...
Json 对象
fun <A> String.fromJson(type: Class<A>): A {
return Gson().fromJson(this, type)
}
对象到 Json 字符串
fun <A> A.toJson(): String? {
return Gson().toJson(this)
}
用户 NavType.StringType 而不是 NavType.ParcelableType..
composable("detail/{item}",
arguments = listOf(navArgument("item") {
type = NavType.StringType
})
) {
it.arguments?.getString("item")?.let { jsonString ->
val user = jsonString.fromJson(User::class.java)
DetailScreen( navController = navController, user = user )
}
}
现在通过传递字符串进行导航..
val userString = user.toJson()
navController.navigate(detail/$userString")
编辑:您可以导航的 Json-String 也有限制。如果 Json-String 的长度太长,那么 NavController 将无法识别您的可组合路由,最终会抛出异常……另一种解决方法是使用全局变量并在导航前设置其值。 . 然后将此值作为参数传递到可组合函数中..
var globalUser : User? = null // global object somewhere in your code
.....
.....
// While Navigating
click { user->
globalUser = user
navController.navigate(detail/$userString")
}
// Composable
composable( "detail") {
DetailScreen(
navController = navController,
globalUser)
}
注意 :-> ViewModels 也可以用来实现这个..
从 documentation 开始,我可以传递字符串、整数等。但是如何在导航中传递对象?
注意:如果我将参数类型设置为 parcelable,则应用会崩溃并显示 java.lang.UnsupportedOperationException: Parcelables don't support default values.
。
composable(
"vendor/details/{vendor}",
arguments = listOf(navArgument("vendor") {
type = NavType.ParcelableType(Vendor::class.java)
})
) {
// ...
}
以下解决方法基于 navigation-compose
版本 2.4.0-alpha05
。
我找到了 2 个传递对象的解决方法。
1.将对象转换成JSON字符串:
这里我们可以使用对象的 JSON 字符串表示来传递对象。
示例代码:
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)
UserDetailsView(userObject) // Here UserDetailsView is a composable.
}
}
// Composable function/view
@Composable
fun UserDetailsView(
user: User
){
// ...
}
2。使用 NavBackStackEntry
:
这里我们可以使用navController.currentBackStackEntry
传递数据,使用navController.previousBackStackEntry
接收数据。
示例代码:
val ROUTE_USER_DETAILS = "user-details/{user}"
// Pass data
val user = User(id = 1, name = "John Doe") // User is a parcelable data class.
navController.currentBackStackEntry?.arguments?.putParcelable("user", user)
navController.navigate(ROUTE_USER_DETAILS)
// Receive data
NavHost {
composable(ROUTE_USER_DETAILS) { backStackEntry ->
val userObject = navController.previousBackStackEntry?.arguments?.getParcelable<User>("user")
UserDetailsView(userObject) // Here UserDetailsView is a composable.
}
}
// Composable function/view
@Composable
fun UserDetailsView(
user: User
){
// ...
}
重要说明:如果我们在导航时弹出返回堆栈,第二种解决方案将不起作用。
Parcelables 目前不支持默认值,因此您需要将对象作为字符串值传递。是的,这是一种解决方法。因此,我们可以将该对象转换为 JSON(字符串)并通过导航传递,然后将 JSON 解析回目的地的对象,而不是将对象本身作为 Parcelize 对象传递.您可以使用 GSON 进行对象到 json 字符串的转换...
Json 对象
fun <A> String.fromJson(type: Class<A>): A {
return Gson().fromJson(this, type)
}
对象到 Json 字符串
fun <A> A.toJson(): String? {
return Gson().toJson(this)
}
用户 NavType.StringType 而不是 NavType.ParcelableType..
composable("detail/{item}",
arguments = listOf(navArgument("item") {
type = NavType.StringType
})
) {
it.arguments?.getString("item")?.let { jsonString ->
val user = jsonString.fromJson(User::class.java)
DetailScreen( navController = navController, user = user )
}
}
现在通过传递字符串进行导航..
val userString = user.toJson()
navController.navigate(detail/$userString")
编辑:您可以导航的 Json-String 也有限制。如果 Json-String 的长度太长,那么 NavController 将无法识别您的可组合路由,最终会抛出异常……另一种解决方法是使用全局变量并在导航前设置其值。 . 然后将此值作为参数传递到可组合函数中..
var globalUser : User? = null // global object somewhere in your code
.....
.....
// While Navigating
click { user->
globalUser = user
navController.navigate(detail/$userString")
}
// Composable
composable( "detail") {
DetailScreen(
navController = navController,
globalUser)
}
注意 :-> ViewModels 也可以用来实现这个..