简体   繁体   English

通过组合导航传递 Parcelable 参数

[英]Pass Parcelable argument with compose navigation

I want to pass a parcelable object ( BluetoothDevice ) to a composable using compose navigation.我想使用 compose 导航将 parcelable object ( BluetoothDevice ) 传递给可组合项。

Passing primitive types is easy:传递原始类型很容易:

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

But I can't pass a parcelable object in the route unless I can serialize it to a string.但是我不能在路由中传递一个 parcelable object 除非我可以将它序列化为一个字符串。

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

The code above obviously doesn't work because it just implicitly calls toString() .上面的代码显然不起作用,因为它只是隐式调用了toString()

Is there a way to either serialize a Parcelable to a String so I can pass it in the route or pass the navigation argument as an object with a function other than navigate(route: String) ?有没有办法将Parcelable序列化为String以便我可以在路由中传递它,或者将导航参数作为 object 和 function 而不是navigate(route: String)传递?

Warning: Ian Lake is an Android Developer Advocate and he says in this answer that pass complex data structures is an anti-pattern (referring the documentation).警告: Ian Lake是 Android 开发倡导者,他在这个答案中说,传递复杂的数据结构是一种反模式(参考文档)。 He works on this library, so he has authority on this.他在这个图书馆工作,所以他对此有权威。 Use the approach below by your own.请自行使用以下方法。

Edit: Updated to Compose Navigation 2.4.0-beta07编辑:更新为 Compose Navigation 2.4.0-beta07

Seems like previous solution is not supported anymore.似乎不再支持以前的解决方案。 Now you need to create a custom NavType .现在您需要创建一个自定义NavType

Let's say you have a class like:假设您有一个 class ,例如:

@Parcelize
data class Device(val id: String, val name: String) : Parcelable

Then you need to define a NavType然后你需要定义一个NavType

class AssetParamType : NavType<Device>(isNullableAllowed = false) {
    override fun get(bundle: Bundle, key: String): Device? {
        return bundle.getParcelable(key)
    }

    override fun parseValue(value: String): Device {
        return Gson().fromJson(value, Device::class.java)
    }

    override fun put(bundle: Bundle, key: String, value: Device) {
        bundle.putParcelable(key, value)
    }
}

Notice that I'm using Gson to convert the object to a JSON string.请注意,我使用Gson将 object 转换为 JSON 字符串。 But you can use the conversor that you prefer...但是您可以使用您喜欢的转换器...

Then declare your composable like this:然后像这样声明你的可组合:

NavHost(...) {
    composable("home") {
        Home(
            onClick = {
                 val device = Device("1", "My device")
                 val json = Uri.encode(Gson().toJson(device))
                 navController.navigate("details/$json")
            }
        )
    }
    composable(
        "details/{device}",
        arguments = listOf(
            navArgument("device") {
                type = AssetParamType()
            }
        )
    ) {
        val device = it.arguments?.getParcelable<Device>("device")
        Details(device)
    }
}

Original answer原始答案

Basically you can do the following:基本上你可以做到以下几点:

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

And in the details screen...在详细信息屏幕中...

val device = navController.previousBackStackEntry
    ?.arguments?.getParcelable<BluetoothDevice>("bt_device")

I've written a small extension for the NavController.我为 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)
    }
}

As you can check there are at least 16 functions " navigate " with different parameters, so it's just a converter for use如您所见,至少有 16 个功能“导航”具有不同的参数,所以它只是一个使用的转换器

public open fun navigate(@IdRes resId: Int, args: Bundle?) 

So using this extension you can use Compose Navigation without these terrible deep link parameters for arguments at routes.因此,使用此扩展程序,您可以在没有这些可怕的 arguments 的深度链接参数的情况下使用 Compose Navigation。

Here's my version of using the BackStackEntry这是我使用BackStackEntry的版本

Usage:用法:

composable("your_route") { entry ->
    AwesomeScreen(entry.requiredArg("your_arg_key"))
}
navController.navigate("your_route", "your_arg_key" to yourArg)

Extensions:扩展:

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" }
    }
}

The backStackEntry solution given by @nglauber will not work if we pop up ( popUpTo(...) ) back stacks on navigate(...) .如果我们在navigate(...)上弹出( popUpTo(...) )回栈,@nglauber 给出的 backStackEntry 解决方案将不起作用。

So here is another solution.所以这是另一个解决方案。 We can pass the object by converting it to a JSON string.我们可以通过将 object 转换为 JSON 字符串来传递它。

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)

        UserDetailsView(userObject) // Here UserDetailsView is a composable.
    }
}


// Composable function/view
@Composable
fun UserDetailsView(
    user: User
){
    // ...
}

Here is another solution that works also by adding the Parcelable to the correct NavBackStackEntry , NOT the previous entry .这是另一个解决方案,它也可以通过将 Parcelable 添加到正确的NavBackStackEntry不是上一个条目来工作。 The idea is first to call navController.navigate , then add the argument to the last NavBackStackEntry.arguments in the NavController.backQueue .这个想法是首先调用navController.navigate ,然后将参数添加到NavBackStackEntry.arguments中的最后一个NavController.backQueue Be mindful that this does use another library group restricted API (annotated with RestrictTo(LIBRARY_GROUP) ), so could potentially break.请注意,这确实使用了另一个受限制的库组 API(使用RestrictTo(LIBRARY_GROUP)注释),因此可能会中断。 Solutions posted by some others use the restricted NavBackStackEntry.arguments , however NavController.backQueue is also restricted.其他一些人发布的解决方案使用受限制的NavBackStackEntry.arguments ,但是NavController.backQueue也受到限制。

Here are some extensions for the NavController for navigating and NavBackStackEntry for retrieving the arguments within the route composable:以下是用于导航的NavController和用于在可组合路由中检索NavBackStackEntry的 NavBackStackEntry 的一些扩展:


fun NavController.navigate(
    route: String,
    navOptions: NavOptions? = null,
    navigatorExtras: Navigator.Extras? = null,
    args: List<Pair<String, Parcelable>>? = null,
) {
    if (args == null || args.isEmpty()) {
        navigate(route, navOptions, navigatorExtras)
        return
    }
    navigate(route, navOptions, navigatorExtras)
    val addedEntry: NavBackStackEntry = backQueue.last()
    val argumentBundle: Bundle = addedEntry.arguments ?: Bundle().also {
        addedEntry.arguments = it
    }
    args.forEach { (key, arg) ->
        argumentBundle.putParcelable(key, arg)
    }
}

inline fun <reified T : Parcelable> NavController.navigate(
    route: String,
    navOptions: NavOptions? = null,
    navigatorExtras: Navigator.Extras? = null,
    arg: T? = null,
    
) {
    if (arg == null) {
        navigate(route, navOptions, navigatorExtras)
        return
    }
    navigate(
        route = route,
        navOptions = navOptions,
        navigatorExtras = navigatorExtras,
        args = listOf(T::class.qualifiedName!! to arg),
    )
}

fun NavBackStackEntry.requiredArguments(): Bundle = arguments ?: throw IllegalStateException("Arguments were expected, but none were provided!")

@Composable
inline fun <reified T : Parcelable> NavBackStackEntry.rememberRequiredArgument(
    key: String = T::class.qualifiedName!!,
): T = remember {
    requiredArguments().getParcelable<T>(key) ?: throw IllegalStateException("Expected argument with key: $key of type: ${T::class.qualifiedName!!}")
}

@Composable
inline fun <reified T : Parcelable> NavBackStackEntry.rememberArgument(
    key: String = T::class.qualifiedName!!,
): T? = remember {
    arguments?.getParcelable(key)
}

To navigate with a single argument, you can now do this in the scope of a NavGraphBuilder :要使用单个参数导航,您现在可以在 NavGraphBuilder 的NavGraphBuilder中执行此操作:

composable(route = "screen_1") {
    Button(
        onClick = {
            navController.navigate(
                route = "screen_2",
                arg = MyParcelableArgument(whatever = "whatever"),
            )
        }
    ) {
        Text("goto screen 2")
    }
}
composable(route = "screen_2") { entry ->
    val arg: MyParcelableArgument = entry.rememberRequiredArgument()
    // TODO: do something with arg
}

Or if you want to pass multiple arguments of the same type:或者如果你想传递多个相同类型的 arguments:

composable(route = "screen_1") {
    Button(
        onClick = {
            navController.navigate(
                route = "screen_2",
                args = listOf(
                    "arg_1" to MyParcelableArgument(whatever = "whatever"),
                    "arg_2" to MyParcelableArgument(whatever = "whatever"),
                ),
            )
        }
    ) {
        Text("goto screen 2")
    }
}
composable(route = "screen_2") { entry ->
    val arg1: MyParcelableArgument = entry.rememberRequiredArgument(key = "arg_1")
    val arg2: MyParcelableArgument = entry.rememberRequiredArgument(key = "arg_2")
    // TODO: do something with args
}

The key benefit of this approach is that similar to the answer that uses Moshi to serialise the argument, it will work when popUpTo is used in the navOptions , but will also be more efficient as no JSON serialisation is involved.这种方法的主要好处是,类似于使用 Moshi 序列化参数的答案,当在popUpTo中使用navOptions时它会起作用,但也会更有效,因为不涉及 JSON 序列化。

This will of course not work with deep links, but it will survive process or activity recreation.这当然不适用于深层链接,但它会在过程或活动重新创建中存活下来。 For cases where you need to support deep links or even just optional arguments to navigation routes, you can use the entry.rememberArgument extension.对于需要支持深度链接甚至只是可选的 arguments 导航路线的情况,您可以使用entry.rememberArgument扩展。 Unlike entry.rememberRequiredArgument , it will return null instead of throwing an IllegalStateException .entry.rememberRequiredArgument不同,它将返回 null 而不是抛出IllegalStateException

Following nglauber suggestion, I've created two extensions which are helping me a bit按照nglauber建议,我创建了两个对我有所帮助的扩展

@Suppress("UNCHECKED_CAST")
fun <T> NavHostController.getArgument(name: String): T {
    return previousBackStackEntry?.arguments?.getSerializable(name) as? T
        ?: throw IllegalArgumentException()
}

fun NavHostController.putArgument(name: String, arg: Serializable?) {
    currentBackStackEntry?.arguments?.putSerializable(name, arg)
}

And I use them this way:我这样使用它们:

Source:
navController.putArgument(NavigationScreens.Pdp.Args.game, game)
navController.navigate(NavigationScreens.Pdp.route)

Destination:
val game = navController.getArgument<Game>(NavigationScreens.Pdp.Args.game)
PdpScreen(game)

you can pass an argument like this你可以像这样传递一个论点

val data = DetailScreenArgument(title = "Sample")

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<DetailScreenArgument>(
    "detailArgument"
)

You can use my own solution: https://github.com/usmonie/compose_navigation_with_parcelable_arguments你可以使用我自己的解决方案: https://github.com/usmonie/compose_navigation_with_parcelable_arguments

With my solution, you will be able to get parcelable values directly into the composable function使用我的解决方案,您将能够将可打包的值直接放入可组合的 function 中

I get a weird bug when I implement the top answer to this question.当我实现这个问题的最佳答案时,我遇到了一个奇怪的错误。 Like I have the following Parcelable to pass between two screens of my Jetpack Compose app:就像我有以下 Parcelable 在我的 Jetpack Compose 应用程序的两个屏幕之间传递:

@Parcelize
data class EdgeParcelable(
    val node: NodeParcelable?,
    val cursor: String?,
) : Parcelable

And as the accepted Answer says I have implemented a custom NavType:正如公认的答案所说,我已经实现了自定义 NavType:

class EdgeParcelableType : NavType<EdgeParcelable>(isNullableAllowed = false) {

    override val name: String
        get() = "edge"

    override fun get(bundle: Bundle, key: String): EdgeParcelable? {
        return bundle.getParcelable(key)
    }

    override fun parseValue(value: String): EdgeParcelable {
        return Gson().fromJson(value, EdgeParcelable::class.java)
    }

    override fun put(bundle: Bundle, key: String, value: EdgeParcelable) {
        bundle.putParcelable(key, value)
    }
}

And in my Composable function where I create the NavHost I have:在我创建 NavHost 的 Composable function 中:

@Composable
fun MyApp(viewModel: MyViewModel, modifier: Modifier = Modifier) {
    val navController = rememberNavController()
    Scaffold(
        modifier = modifier.fillMaxSize(),
        topBar = { MyTopAppBar(
            currentScreen=currentScreen,
            canNavigateBack = navController.previousBackStackEntry != null,
            navigateUp = { navController.navigateUp() }
        ) }
    ) { innerPadding ->
        NavHost(
            navController = navController,
            startDestination = "home",
            modifier = Modifier.padding(innerPadding)
        ) {
            composable(route = "home") {
                val lazyPagingItems = viewModel.Items()
                HomeScreen(
                    lazyPagingItems = lazyPagingItems,
                    onTownClicked = { edge: EdgeParcelable ->
                        val json = Uri.encode(Gson().toJson(edgeParcelable))
                      
                     navController.navigateSingleTopTo("hotspots/$json")
                    },
                    modifier = ...
                )
            }
            composable(
                route = "hotspots/{edge}",
                arguments = listOf(
                    navArgument("edge") {
                        type = EdgeParcelableType()
                    }
                )
            ) {
                val edgeParcelable = it.arguments?.getParcelable<EdgeParcelable>("edge")
                HotspotsScreen(edgeParcelable)
            }
        }
    }
}

The code above crashes my Application when I add the lines:当我添加以下行时,上面的代码使我的应用程序崩溃:

val bsEntry by navController.currentBackStackEntryAsState()
val currentScreen = bsEntry?.destination?.route ?: "Home"

Adding the above lines make the Composable become:添加以上行使可组合项变为:

@Composable
fun MyApp(viewModel: MyViewModel, modifier: Modifier = Modifier) {
    val navController = rememberNavController()

    // Adding these causes a problem...
    val bsEntry by navController.currentBackStackEntryAsState()
    val currentScreen = bsEntry?.destination?.route ?: "Home"

    Scaffold(
        modifier = modifier.fillMaxSize(),
        topBar = { MyTopAppBar(
            currentScreen=currentScreen,
            canNavigateBack = navController.previousBackStackEntry != null,
            navigateUp = { navController.navigateUp() }
        ) }
    ) { innerPadding ->
        NavHost(
            navController = navController,
            startDestination = "home",
            modifier = Modifier.padding(innerPadding)
        ) {
            composable(route = "home") {
                val lazyPagingItems = viewModel.Items()
                HomeScreen(
                    lazyPagingItems = lazyPagingItems,
                    onTownClicked = { edge: EdgeParcelable ->
                        val json = Uri.encode(Gson().toJson(edgeParcelable))
                     navController.navigateSingleTopTo("hotspots/$json")
                    },
                    modifier = ...
                )
            }
            composable(
                route = "hotspots/{edge}",
                arguments = listOf(
                    navArgument("edge") {
                        type = EdgeParcelableType()
                    }
                )
            ) {
                val edgeParcelable = it.arguments?.getParcelable<EdgeParcelable>("edge")
                HotspotsScreen(edgeParcelable)
            }
        }
    }
}

Passing my Custom NavType with the following line of code:使用以下代码行传递我的自定义 NavType:

arguments = listOf(navArgument("edge") { type = EdgeParcelableType() } )

now crashes my app, by rendering it unusable.现在使我的应用程序崩溃,使其无法使用。 The app seems to choke on itself, almost like, the Navigation API does not really understand the new Custom EdgeParcleableType() or perhaps something is missing that remains to be added to make this EdgeParcelableType work well with the Navigation API.该应用程序似乎自我窒息,几乎就像导航 API 并没有真正理解新的自定义EdgeParcleableType()或者可能缺少一些有待添加的东西,以使这个EdgeParcelableType与导航 API 一起工作。

I was only able to solve the problem by changing the type above to StringType as follows:我只能通过将上面的类型更改为 StringType 来解决问题,如下所示:

arguments = listOf( navArgument("edge") { type = NavType.StringType }

And passing around strings in the rest of the Composable as follows:并在 Composable 的 rest 中传递字符串,如下所示:

@Composable
fun MyApp(viewModel: MyViewModel, modifier: Modifier = Modifier) {
    val navController = rememberNavController()

    // Using NavType.StringType allows this work...
    val bsEntry by navController.currentBackStackEntryAsState()
    val currentScreen = bsEntry?.destination?.route ?: "Home"

    Scaffold(
        modifier = modifier.fillMaxSize(),
        topBar = { MyTopAppBar(
            currentScreen=currentScreen,
            canNavigateBack = navController.previousBackStackEntry != null,
            navigateUp = { navController.navigateUp() }
        ) }
    ) { innerPadding ->
        NavHost(
            navController = navController,
            startDestination = "home",
            modifier = Modifier.padding(innerPadding)
        ) {
            composable(route = "home") {
                val lazyPagingItems = viewModel.Items()
                HomeScreen(
                    lazyPagingItems = lazyPagingItems,
                    onTownClicked = { edge: EdgeParcelable ->
                        val json = Uri.encode(Gson().toJson(edgeParcelable))
                     navController.navigateSingleTopTo("hotspots/$json")
                    },
                    modifier = ...
                )
            }
            composable(
                route = "hotspots/{edge}",
                arguments = listOf( navArgument("edge") {
                        type = NavType.StringType
                    }
                )
            ) {
                val edgeParcelable = Gson().fromJson(Uri.decode(it.arguments?.getString("edge")), EdgeParcelable::class.java)
                HotspotsScreen(edgeParcelable)
            }
        }
    }
}

Then my app worked like a Charm.然后我的应用程序就像一个魅力。 Sorting this took me like 2 days of trial and error and searching so I hope this can help someone out there if faced with a similar issue...对此进行排序花了我 2 天的时间进行反复试验和搜索,所以我希望这可以帮助遇到类似问题的人......

I had a similar issue where I had to pass a string that contains slashes, and since they are used as separators for deep link arguments I could not do that.我有一个类似的问题,我必须传递一个包含斜杠的字符串,因为它们被用作深度链接 arguments 的分隔符,所以我不能这样做。 Escaping them didn't seem "clean" to me. Escaping 它们对我来说似乎并不“干净”。

I came up with the following workaround, which can be easily tweaked for your case.我想出了以下解决方法,可以根据您的情况轻松调整。 I rewrote NavHost , NavController.createGraph and NavGraphBuilder.composable from androidx.navigation.compose as follows:我从androidx.navigation.compose重写了NavHostNavController.createGraphNavGraphBuilder.composable ,如下所示:

@Composable
fun NavHost(
    navController: NavHostController,
    startDestination: Screen,
    route: String? = null,
    builder: NavGraphBuilder.() -> Unit
) {
    NavHost(navController, remember(route, startDestination, builder) {
        navController.createGraph(startDestination, route, builder)
    })
}

fun NavController.createGraph(
    startDestination: Screen,
    route: String? = null,
    builder: NavGraphBuilder.() -> Unit
) = navigatorProvider.navigation(route?.hashCode() ?: 0, startDestination.hashCode(), builder)

fun NavGraphBuilder.composable(
    screen: Screen,
    content: @Composable (NavBackStackEntry) -> Unit
) {
    addDestination(ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
        id = screen.hashCode()
    })
}

Where Screen is my destination enum Screen 是我的目标枚举在哪里

sealed class Screen {
    object Index : Screen()
    object Example : Screen()
}

Please note that I removed deep links and arguments since I am not using them.请注意,我删除了深层链接和 arguments,因为我没有使用它们。 That will still allow me to pass and retrieve arguments manually, and that functionality can be re-added, I simply didn't need it for my case.这仍然允许我手动传递和检索 arguments,并且可以重新添加该功能,我根本不需要它。

Say I want Example to take a string argument path假设我希望Example采用字符串参数path

const val ARG_PATH = "path"

I then initialise the NavHost like so然后我像这样初始化 NavHost

NavHost(navController, startDestination = Screen.Index) {
    composable(Screen.Index) { IndexScreen(::navToExample) }

    composable(Screen.Example) { navBackStackEntry ->
        navBackStackEntry.arguments?.getString(ARG_PATH)?.let { path ->
            ExampleScreen(path, ::navToIndex)
        }
    }
}

And this is how I navigate to Example passing path这就是我导航到Example传递path的方式

fun navToExample(path: String) {
    navController.navigate(Screen.Example.hashCode(), Bundle().apply {
        putString(ARG_PATH, path)
    })
}

I am sure that this can be improved, but these were my initial thoughts.我确信这可以改进,但这是我最初的想法。 To enable deep links, you will need to revert back to using要启用深层链接,您需要恢复使用

// composable() and other places
val internalRoute = "android-app://androidx.navigation.compose/$route"
id = internalRoute.hashCode()

Since the nglauber's answer work when going forward and does not when navigating backward and you get a null.由于nglauber 的答案在前进时有效,而在向后导航时无效,您会得到 null。 I thought maybe at least for the time being we can save the passed argument using remember in our composable and be hopeful that they add the Parcelable argument type to the navigating with the route.我想也许至少暂时我们可以在我们的可组合文件中使用记住来保存传递的参数,并希望他们将 Parcelable 参数类型添加到路由导航中。

the destination composable target:目标可组合目标:

composable("yourRout") { backStackEntry ->
                backStackEntry.arguments?.let {
                    val rememberedProject = remember { mutableStateOf<Project?>(null) }
                    val project =
                        navController.previousBackStackEntry?.arguments?.getParcelable(
                            PROJECT_ARGUMENT_KEY
                        ) ?: rememberedProject.value
                    rememberedProject.value = project
                    TargetScreen(
                        project = project ?: throw IllegalArgumentException("parcelable was null"),
                    )
                }

And here's the the source code: to trigger the navigation:这是源代码:触发导航:

navController.currentBackStackEntry?.arguments =
            Bundle().apply {
                putParcelable(PROJECT_ARGUMENT_KEY, project)
            }
        navController.navigate("yourRout")

A very simple and basic way to do is as below一个非常简单和基本的方法如下

1.First create the parcelable object that you want to pass eg 1.首先创建您要传递的parcelable object 例如

@Parcelize
data class User(
    val name: String,
    val phoneNumber:String
) : Parcelable

2.Then in the current composable that you are in eg main screen 2.然后在您所在的当前可组合中,例如主屏幕

 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 eg 3.然后在可组合的细节中,确保将导航控制器作为参数传递给它,例如

@Composable
fun Details (navController:NavController){
val data = remember {
        mutableStateOf(navController.previousBackStackEntry?.arguments?.getParcelable<UserDetails>("userDetails")!!)
    }
}

NB: If the parcelable is not passed into state, you will receive an error when navigating back NB:如果parcelable没有传入state,你会在导航回来的时候收到一个错误

My approach with Moshi :我对Moshi的态度:

Routes路线

sealed class Route(
    private val route: String,
    val Key: String = "",
) {
    object Main : Route(route = "main")
    object Profile : Route(route = "profile", Key = "user")

    override fun toString(): String {
        return when {
            Key.isNotEmpty() -> "$route/{$Key}"
            else -> route
        }
    }
}

Extension扩大

import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.core.net.toUri
import androidx.navigation.*
import com.squareup.moshi.Moshi

inline fun <reified T> NavController.navigate(
    route: String,
    data: Pair<String, T>,
    navOptions: NavOptions? = null,
    navigatorExtras: Navigator.Extras? = null,
) {
    val count = route
        .split("{${data.first}}")
        .size
        .dec()

    if (count != 1) {
        throw IllegalArgumentException()
    }

    val out = Moshi.Builder()
        .build()
        .adapter(T::class.java)
        .toJson(data.second)
    val newRoute = route.replace(
        oldValue = "{${data.first}}",
        newValue = Uri.encode(out),
    )

    navigate(
        request = NavDeepLinkRequest.Builder
            .fromUri(NavDestination.createRoute(route = newRoute).toUri())
            .build(),
        navOptions = navOptions,
        navigatorExtras = navigatorExtras,
    )
}

inline fun <reified T> NavBackStackEntry.getData(key: String): T? {
    val data = arguments?.getString(key)

    return when {
        data != null -> Moshi.Builder()
            .build()
            .adapter(T::class.java)
            .fromJson(data)
        else -> null
    }
}

@Composable
inline fun <reified T> NavBackStackEntry.rememberGetData(key: String): T? {
    return remember { getData<T>(key) }
}

Example usage示例用法

data class User(
    val id: Int,
    val name: String,
)

@Composable
fun RootNavGraph() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "${Route.Main}",
    ) {
        composable(
            route = "${Route.Main}",
        ) {
           Button(
              onClick = {
                  navController.navigate(
                      route = "${Route.Profile}",
                      data = Route.Profile.Key to User(id = 1000, name = "John Doe"),
                  )
              },
              content = { Text(text = "Go to Profile") },
        }

        composable(
            route = "${Route.Profile}",
            arguments = listOf(
                navArgument(name = Route.Profile.Key) { type = NavType.StringType },
            ),
        ) { entry ->
            val user = entry.rememberGetData<User>(key = Route.Profile.Key)

            Text(text = "$user")
        }
    }
}

1 - Add the name of the data sent in the route 1 - 添加路由中发送的数据的名称

const val NEWS_DETAILS = "NEWS_DETAILS/{article}/{name}"

2 - Create dataClassParcelable 2 - 创建 dataClassParcelable

@Parcelize
data class ArticlesModel(
    @SerializedName("author")
    val author: String?,
    @SerializedName("content")
    val content: String?,
    @SerializedName("description")
    val description: String?,
    @SerializedName("publishedAt")
    val publishedAt: String?,
    @SerializedName("source")
    val source: Strins?,
    @SerializedName("title")
    val title: String?,
    @SerializedName("url")
    val url: String?,
    @SerializedName("urlToImage")
    val urlToImage: String?
):Parcelable

3 - Submit the required data 3 - 提交所需数据

    fun NavController.openNewsDetails(article: ArticlesModel,name:String) {
        currentBackStackEntry?.savedStateHandle?.apply{

             set(
                 "article",
                 article
             )

             set(
                 "name",
                 name
             )
         }

        navigate(ScreenConst.NEWS_DETAILS)
    }

4 - Reading information and sending them to a new screen 4 - 阅读信息并将其发送到新屏幕

          composable(
                    route = ScreenConst.NEWS_DETAILS
                ) { navBackStackEntry ->
            
                    val article = 
navController.previousBackStackEntry?.savedStateHandle?.get<ArticlesModel("article")
                    val name=
navController.previousBackStackEntry?.savedStateHandle?.get<String>("name")
            
                    if (article==null && name.isNullOrEmpty()) {
                        return@composable
                    }
            
                    DetailsScreen(
                        article = article,
                        name = name
                    )
            
                }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用新版本的撰写导航传递 parcelable 参数? - How pass parcelable argument with new version of compose navigation? Jetpack Compose Navigation - 将参数传递给 startDestination - Jetpack Compose Navigation - pass argument to startDestination 将参数传递给 Jetpack Compose 中的嵌套导航图 - Pass an argument to a nested navigation graph in Jetpack Compose 未解决的参考:当使用自定义 Parcelable 参数时:Jetpack Navigation - Unresolved Reference: When Custom Parcelable Argument Used : Jetpack Navigation 是否可以通过导航组件深层链接传递 Parcelable object? - Is it possible to pass Parcelable object via Navigation component deeplink? 撰写导航参数字符串包含花括号? - Compose navigation argument string includes curly braces? 如何通过 Android 组合中的导航传递 object? - How to pass an object through Navigation in Android compose? 导航组件:parcelable 参数但得到错误类型不匹配:推断类型为学生但预期为字符串 - Navigation component: parcelable argument but get error Type mismatch: inferred type is Student but String was expected 传递一个可分辨的对象的arraylist - To pass a parcelable arraylist of objects 通行证清单 <String> 带包裹 - Pass List<String> with Parcelable
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM