Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

367
Vistas
Pass Parcelable argument with compose navigation

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)?

over 4 years ago · Hanz Gallego
8 Respuestas
Responde la pregunta

0

Following nglauber suggestion, I've created two extensions which are helping me a bit

@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)
over 4 years ago · Hanz Gallego Denunciar

0

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 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
){
    // ...
}
over 4 years ago · Hanz Gallego Denunciar

0

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. Escaping them didn't seem "clean" to me.

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:

@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

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

Please note that I removed deep links and arguments since I am not using them. 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.

Say I want Example to take a string argument path

const val ARG_PATH = "path"

I then initialise the NavHost like so

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

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()
over 4 years ago · Hanz Gallego Denunciar

0

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")
over 4 years ago · Hanz Gallego Denunciar

0

Here's my version of using the 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" }
    }
}
over 4 years ago · Hanz Gallego Denunciar

0

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.

over 4 years ago · Hanz Gallego Denunciar

0

Since the nglauber's answer work when going forward and does not when navigating backward and you get a 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.

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")
over 4 years ago · Hanz Gallego Denunciar

0

A very simple and basic way to do is as below

1.First create the parcelable object that you want to pass e.g

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

2.Then in the current composable that you are in e.g main screen

 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 e.g.

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

N.B: If the parcelable is not passed into state, you will receive an error when navigating back

over 4 years ago · Hanz Gallego Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda