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