使用撰写导航传递 Parcelable 参数

Noa*_*oah 13 android kotlin android-jetpack-navigation android-jetpack-compose

我想BluetoothDevice使用组合导航将 Parcelable 对象 ( ) 传递给可组合对象。

传递原始类型很容易:

composable(
  "profile/{userId}",
  arguments = listOf(navArgument("userId") { type = NavType.StringType })
) {...}
Run Code Online (Sandbox Code Playgroud)
navController.navigate("profile/user1234")
Run Code Online (Sandbox Code Playgroud)

但是我不能在路由中传递一个parcelable 对象,除非我可以将它序列化为一个字符串。

composable(
  "deviceDetails/{device}",
  arguments = listOf(navArgument("device") { type = NavType.ParcelableType(BluetoothDevice::class.java) })
) {...}
Run Code Online (Sandbox Code Playgroud)
val device: BluetoothDevice = ...
navController.navigate("deviceDetails/$device")
Run Code Online (Sandbox Code Playgroud)

上面的代码显然不起作用,因为它只是隐式调用toString().

有没有办法要么序列化ParcelableString,所以我可以通过它的路线或通过导航参数作为比其他函数的对象navigate(route: String)

Val*_*yev 36

我为 NavController 编写了一个小扩展。

import android.os.Bundle
import androidx.core.net.toUri
import androidx.navigation.*

fun NavController.navigate(
    route: String,
    args: Bundle,
    navOptions: NavOptions? = null,
    navigatorExtras: Navigator.Extras? = null
) {
    val routeLink = NavDeepLinkRequest
        .Builder
        .fromUri(NavDestination.createRoute(route).toUri())
        .build()

    val deepLinkMatch = graph.matchDeepLink(routeLink)
    if (deepLinkMatch != null) {
        val destination = deepLinkMatch.destination
        val id = destination.id
        navigate(id, args, navOptions, navigatorExtras)
    } else {
        navigate(route, navOptions, navigatorExtras)
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,至少有 16 个带有不同参数的函数“ navigate ”,所以它只是一个可供使用的转换器

public open fun navigate(@IdRes resId: Int, args: Bundle?) 
Run Code Online (Sandbox Code Playgroud)

因此,使用此扩展,您可以使用 Compose Navigation,而无需这些可怕的深度链接参数作为路由参数。

  • 这应该是公认的答案。 (4认同)
  • 你能改进它以及如何使用它吗? (4认同)
  • 看起来不错,但当我尝试它时我得到一个空指针。 (3认同)

ngl*_*ber 15

编辑:更新为beta-07

基本上你可以做到以下几点:

// In the source screen...
navController.currentBackStackEntry?.arguments = 
    Bundle().apply {
        putParcelable("bt_device", device)
    }
navController.navigate("deviceDetails")
Run Code Online (Sandbox Code Playgroud)

在详细信息屏幕中...

val device = navController.previousBackStackEntry
    ?.arguments?.getParcelable<BluetoothDevice>("bt_device")
Run Code Online (Sandbox Code Playgroud)

  • 这对我不起作用:对于“androidx.navigation:navigation-compose:2.4.0-alpha01”“navController.currentBackStackEntry?.arguments”,“arguments”的值对我来说是“null”,因此它是“val”不能重新分配 (13认同)
  • 如果我们在 `navigate(...)` 上弹出 (`popUpTo(...)`) 返回堆栈,则此解决方案将不起作用。 (3认同)
  • 对于使用此解决方案的任何人,请注意导航调用两侧的 currentBackStackEntry 和 previousBackStackEntry 之间的差异。否则您将浪费 20 分钟寻找丢失的数据...;0) (2认同)
  • 请注意,这不会在进程死亡后继续存在。https://issuetracker.google.com/issues/182194894#comment6 (2认同)
  • 等等,这基本上会覆盖当前路线的/屏幕的参数,并且下一个屏幕在导航后不是从自身而是从父级查找它们。这感觉就像是黑客攻击。当系统需要父级获得的原始参数时会发生什么? (2认同)
  • 这是一个不好的做法。尽管我对所有使其成为可能的努力表示庆祝,但 Android 的设计却阻止了这种做法。以下是一位 Google|Android 工程师参考官方文档给出的详细回复。/sf/ask/4834140461/ (2认同)

M. *_*loo 7

这是我使用的版本BackStackEntry

用法:

composable("your_route") { entry ->
    AwesomeScreen(entry.requiredArg("your_arg_key"))
}
navController.navigate("your_route", "your_arg_key" to yourArg)
Run Code Online (Sandbox Code Playgroud)

扩展:

fun NavController.navigate(route: String, vararg args: Pair<String, Parcelable>) {
    navigate(route)
    
    requireNotNull(currentBackStackEntry?.arguments).apply {
        args.forEach { (key: String, arg: Parcelable) ->
            putParcelable(key, arg)
        }
    }
}

inline fun <reified T : Parcelable> NavBackStackEntry.requiredArg(key: String): T {
    return requireNotNull(arguments) { "arguments bundle is null" }.run {
        requireNotNull(getParcelable(key)) { "argument for $key is null" }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 6

  1. 使用 NavGraph#findNode 获取目的地
  2. 使用目的地的id进行导航,该方法有一个args: Bundle?参数
val destination = navController.graph.findNode("YOUR_ROUTE")
if (destination != null) {
  navController.navigate(
  destination.id,
  bundleOf("key" to value)
  )
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ner 5

我想出了这个解决方案:

fun NavController.navigate(
    route: String,
    args: Bundle,
    navOptions: NavOptions? = null,
    navigatorExtras: Navigator.Extras? = null
) {
    val nodeId = graph.findNode(route = route)?.id
    if (nodeId != null) {
        navigate(nodeId, args, navOptions, navigatorExtras)
    }
}
Run Code Online (Sandbox Code Playgroud)

这通过导航图中的ID来识别注册的路线,完全绕过了深层链接机制并回退到原始方法。

在 NavGraphBuilder 中注册您的路线,如下所示:

composable(
    route = "MyRoute",
) {
    MyScreen(
        navController = navController,
        myArgument = it.arguments?.getParcelable("myParcelableArg")
    )
}
Run Code Online (Sandbox Code Playgroud)

并像这样导航:

navController.navigate(
    route = "MyRoute",
    args = bundleOf(
        "myParcelableArg" to MyParcelable()
    )
)
Run Code Online (Sandbox Code Playgroud)