Search code examples
androidandroid-jetpack-composejetpack-compose-navigation

Type safe navigation with argument passing


I’ve been working on implementing type-safe navigation in Jetpack Compose. While it’s functioning well overall, I encountered a crash when passing data between destinations.

Here’s the crash log I received:

https://gist.github.com/Priyanka0818/b2248799d1567122e4c29081a819f6e4

Relevant files for context:

https://gist.github.com/Priyanka0818/fc9eb32335189d1d1aead67ba1ce1016 https://gist.github.com/Priyanka0818/ac7bc16a874dd9bfbaf41f84f91097ac

I followed the official documentation for implementation:

https://developer.android.com/guide/navigation/design/type-safety#graph

To resolve the crash, I explored various articles to see how other developers approached this issue. While the app no longer crashes, I’m not receiving the expected values in the parameters. It seems like there’s an issue with the parsing.

Here’s the updated implementation:

https://gist.github.com/Priyanka0818/f1348aa88c6eb2d0ded8aba08a475285 https://gist.github.com/Priyanka0818/0fdde5e640d5bb5b8e674eea8c698d4c https://gist.github.com/Priyanka0818/47c7a826d65d1565498be882f337969a

I’d love to hear your insights or suggestions for handling this. Let’s collaborate to solve this! 🙌


Solution

  • The problems are in this line of code:

    val args = it.toRoute<Data>()
    

    Must be cast to the ShowIndividualData type.

    Code example:

    @Serializable
    data class Data(
        @SerialName("id") var id: Int? = null,
        @SerialName("email") var email: String? = null,
        @SerialName("first_name") var first_name: String? = null,
        @SerialName("last_name") var last_name: String? = null,
        @SerialName("avatar") var avatar: String? = null
    )
    
    data object TypeSafetyScreens {
    
        @Serializable
        data object Start
    
        @Serializable
        data class Content(val data: Data)
    }
    
    class CustomNavType<T>(private val serializer: KSerializer<T>) :
        NavType<T>(isNullableAllowed = false) {
    
        override fun get(bundle: Bundle, key: String): T? =
            bundle.getString(key)?.let { Json.decodeFromString(serializer, it) }
    
        override fun parseValue(value: String): T =
            Json.decodeFromString(serializer, Uri.decode(value))
    
        override fun serializeAsValue(value: T): String =
            Uri.encode(Json.encodeToString(serializer, value))
    
        override fun put(bundle: Bundle, key: String, value: T) =
            bundle.putString(key, Json.encodeToString(serializer, value))
    }
    
    
    @Composable
    fun TypeSafety() {
        val navController = rememberNavController()
    
        Scaffold(
            content = { paddingValues ->
                NavHost(
                    modifier = Modifier.padding(paddingValues),
                    navController = navController,
                    startDestination = TypeSafetyScreens.Start,
                ) {
                    composable<TypeSafetyScreens.Start> {
                        TypeSafetyStart(navController)
                    }
    
                    composable<TypeSafetyScreens.Content>(
                        typeMap = mapOf(typeOf<Data>() to CustomNavType(Data.serializer()))
                    ) { backStackEntry ->
                        val data = backStackEntry.toRoute<TypeSafetyScreens.Content>().data
                        TypeSafetyContent(data)
                    }
                }
            }
        )
    }
    
    @Composable
    fun TypeSafetyStart(navController: NavHostController) {
        LaunchedEffect(Unit) {
            navController.navigate(
                TypeSafetyScreens.Content(
                    Data(
                        id = 1,
                        email = "email",
                        first_name = "firstName",
                        last_name = "lastName",
                        avatar = "avatar"
                    )
                )
            )
        }
    }
    
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun TypeSafetyContent(data: Data) {
        Scaffold(topBar = { TopAppBar(title = { Text("Content") }) }) { paddingValues ->
            Column(
                Modifier
                    .padding(paddingValues)
                    .padding(horizontal = 16.dp)
            ) {
                Text("id = ${data.id}")
                Text("email = ${data.email}")
                Text("first_name = ${data.first_name}")
                Text("last_name = ${data.last_name}")
                Text("avatar = ${data.avatar}")
            }
        }
    }