I want to pass a parcelable object (BluetoothDevice
) to a composable using compose navigation.
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.
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()
.
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)
?
In the view system navigation component, we were able to pass parcelable objects as arguments to the destinations, you might be tempted to do that in Compose Navigation as well and write up code that looks like this:
NOTE: The Jetpack Compose team doesn’t recommend passing Parcelable in the navigation composable routes.
Navigation compose also supports passing arguments between composable destinations. In order to do this, you need to add argument placeholders to your route, similar to how you add arguments to a deep link when using the base navigation library: NavHost (startDestination = "profile/ {userId}") { ... composable ("profile/ {userId}") {...} }
The way it is currently done in jetpack compose is by appending the route with arguments and their respective values. For example, let’s say that we have a composable with route = “userPage”, and we want to pass arguments “userId” and “isLoggedIn”. The following snippets show how to do that in jetpack compose.
Edit: Updated to Compose Navigation 2.4.0-beta07
Seems like previous solution is not supported anymore. Now you need to create a custom NavType
.
Let's say you have a class like:
@Parcelize data 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) } }
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With