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! 🙌
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}")
}
}
}