2

I'm using Jetpack Compose Navigation to pass a Health instance to another composable. The below code shows my health class and my Destination.

Health.kt:

data class Health(
    val height: Int,
    val weight: Int,
    val age: Int,
    val gender: Gender
) : Serializable

enum class Gender: Serializable { Male, Female }

NavDestination.kt

                        composable(
                            route = "result/{health}",
                            arguments = listOf(
                                navArgument("health") {
                                    type =
                                        NavType.SerializableType(Health::class.java)
                                }
                            )
                        ) { backStackEntry ->
                            val health = (backStackEntry.arguments?.getSerializable("health") as? Health) ?: return@composable
                            ResultScreen(navActions = navActions, health = health)
                        }

NavActions.kt

val navigateToResultScreen = { health: Health ->
        navController.navigate("result/{$health}")
    }

However, I'm getting this error according to the logcat. Any assistance would be highly appreciated.

 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.octagon_technologies.bmicalculator, PID: 20788
    java.lang.UnsupportedOperationException: Serializables don't support default values.
        at androidx.navigation.NavType$SerializableType.parseValue(NavType.java:838)
        at androidx.navigation.NavType$SerializableType.parseValue(NavType.java:791)
        at androidx.navigation.NavType.parseAndPut(NavType.java:96)
        at androidx.navigation.NavDeepLink.parseArgument(NavDeepLink.java:299)
        at androidx.navigation.NavDeepLink.getMatchingArguments(NavDeepLink.java:260)
        at androidx.navigation.NavDestination.matchDeepLink(NavDestination.java:474)
        at androidx.navigation.NavGraph.matchDeepLink(NavGraph.java:79)
        at androidx.navigation.NavController.navigate(NavController.java:1034)
        at androidx.navigation.NavController.navigate(NavController.java:1017)
        at androidx.navigation.compose.NavHostControllerKt.navigate(NavHostController.kt:107)
        at androidx.navigation.compose.NavHostControllerKt.navigate$default(NavHostController.kt:106)
        at com.octagon_technologies.bmicalculator.ui.navigation.NavActions$navigateToResultScreen$1.invoke(NavActions.kt:9)
        at com.octagon_technologies.bmicalculator.ui.navigation.NavActions$navigateToResultScreen$1.invoke(NavActions.kt:8)
        at com.octagon_technologies.bmicalculator.ui.screens.home.HomeScreenKt$HomeScreen$1$6.invoke(HomeScreen.kt:122)
        at com.octagon_technologies.bmicalculator.ui.screens.home.HomeScreenKt$HomeScreen$1$6.invoke(HomeScreen.kt:120)
        at com.octagon_technologies.bmicalculator.ui.components.home.CalculateBtnKt$CalculateButton$1$1$1.invoke(CalculateBtn.kt:29)
        at com.octagon_technologies.bmicalculator.ui.components.home.CalculateBtnKt$CalculateButton$1$1$1.invoke(CalculateBtn.kt:29)
Andrew Chelix
  • 1,012
  • 11
  • 16

3 Answers3

4

Why this couldn't work: https://issuetracker.google.com/issues/148523779

"Serializable and Parcelable have no consistent API surface that would allow automatic parsing of a string into your custom class instance. That's why parseValue throws an UnsupportedOperationException."

It's expected that you'll only pass ids in this deep links, not full objects

Workaround:

composable(
    route = "result",
    arguments = listOf(
        navArgument("health") {
            type =
                NavType.SerializableType(Health::class.java)
        }
    )
) {
    val health = (navController.previousBackStackEntry.arguments?.getSerializable("health") as? Health) ?: return@composable
    ResultScreen(navActions = navActions, health = health)
}

Navigate like this:

navController.currentBackStackEntry
    ?.arguments?.putSerializable("health", health)
navController.navigate("result")
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • Looks like a nice workaround. Unfortunately, when I try it, `navController.currentBackStackEntry.arguments` and `navController.previousBackStackEntry.arguments` are always null. Is there something missing from your example? Maybe something special in the way you initialise `navController`? All examples I've seen so far just have ```val navController = rememberNavController()```. – Myles Bennett Jul 12 '22 at 10:45
  • @MylesBennett `previousBackStackEntry ` should be available if you have a previous screen in the stack. It's easy to check out - does pressing back button leads to the previous screen or closes the app? Do you have any parameters specified with `navController.navigate`? – Phil Dukhov Jul 12 '22 at 11:46
  • So this is just a really simple project I set up just to test this exact functionality. There are two screens/stack entries. I'm trying to move from the first screen to the second passing a serializable data object from that first screen to the second. Pressing back from the first screen will obviously exit the app. At some point, something has to set `arguments` to something non-null. I guess I'm missing that bit. And yes, I had parameters set in `navController.navigate`, including a dummy for the first screen. – Myles Bennett Jul 19 '22 at 09:49
  • @PhilDukhov Can you please help me out over here: https://stackoverflow.com/questions/73650798/how-pass-parcelable-object-with-new-version-of-compose-navigation?noredirect=1#comment130059237_73650798 – InsaneCat Sep 09 '22 at 10:19
0

I have the same issue, but I've fixed it using the Gson library, which is used for encoding & decoding classes from JSON.

Will to use it you will convert your Class into JSON as a String and pass it into a navigation argument as a String type then you'll decode it from String which the JSON code into your Class again.

Example of use

navController.navigate("result/${Gson().toJson($health)}")

Now for decoding our Class again

composable(
    route = "result/{health}",
    arguments = listOf(navArgument("health") { type = NavType.StringType })
) { backStackEntry ->
    val healthAsJson = backStackEntry.arguments?.getString("health")
    val health = Gson().fromJson(healthAsJson, Health::class.java)
    ResultScreen(navActions = navActions, health = health)
}
ABDO-AR
  • 160
  • 3
  • 9
0

Instead of passing a complex Object around, it's better to pass an id and retrieve it by the id. The Navigation documentation states the following:

It is strongly advised not to pass around complex data objects when navigating, but instead pass the minimum necessary information, such as a unique identifier or other form of ID, as arguments when performing navigation actions

leo
  • 113
  • 3
  • 11