【问题标题】:How to pass object in navigation in jetpack compose?如何在jetpack compose的导航中传递对象?
【发布时间】:2021-04-16 08:07:04
【问题描述】:

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)
        })
) {
// ...
}

【问题讨论】:

标签: android-jetpack-compose jetpack-compose-navigation


【解决方案1】:

以下变通方法基于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
){
    // ...
}

重要提示:如果我们在导航时弹出返回堆栈,则第二种解决方案将不起作用。

【讨论】:

  • 新版本有什么解决方法吗?当我使用它时,我的应用程序崩溃了!我的导航版本是2.4.0-alpha03
  • 使用第一种解决方案。解决方案已更新。现在使用可选参数。
  • 如果您的数据类包含 url,第一个解决方案也不起作用。导航由于某种原因找不到它并导致 IllegalArgumentException,正如我所问的 here。例如,如果您的数据类包含图像的 url,请注意。
  • @Thracian 如果您使用 Moshi,在生成 JSON 字符串后,使用 URLEncoder.encode(jsonString, "utf-8") 对字符串进行编码。它应该可以解决问题。你也可以像 fun String.urlEncode(): String = URLEncoder.encode(this, "utf-8") 这样创建一个扩展函数,并像这样使用它:jsonAdapter.toJson(obj).urlEncode()
【解决方案2】:

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 将无法识别您的 Composable Route 最终会引发异常......另一种解决方法是使用全局变量并在导航之前设置其值......然后传递这个值作为可组合函数中的参数..

 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 也可以用来实现这一点..

【讨论】:

  • 这个解决方案已经给here了。
  • 他的解决方案的唯一区别是 GSON 的使用(moshi 用于已经给出的解决方案)。
猜你喜欢
  • 1970-01-01
  • 2020-04-24
  • 1970-01-01
  • 1970-01-01
  • 2022-09-30
  • 2023-03-28
  • 2021-10-13
  • 1970-01-01
  • 2021-10-18
相关资源
最近更新 更多