Pass Parcelable argument with compose navigation Pass Parcelable argument with compose navigation android android

Pass Parcelable argument with compose navigation


Edit: Updated to Compose 1.0.3 and Navigation 2.4.0-alpha10

Seems like previous solution is not supported anymore. Now you need to create a custom NavType.

Let's say you have a class like:

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

Then you need to define a 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. 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)    }}

Edit: Updated to beta-07

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.

import android.os.Bundleimport androidx.core.net.toUriimport 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

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.


The backStackEntry solution given by @nglauber will not work if we pop up (popUpTo(...)) back stacks on navigate(...).

So here is another solution. We can pass the object by converting it to a JSON string.

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 DataNavHost {    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@Composablefun UserDetailsView(    user: User){    // ...}