Type Safe arguments in Navigation Compose on Android

Mubarak Native
4 min readJun 5, 2024

--

In this article we are going to see how Type Safe args in Navigation Compose eliminates the boilerplate, and how it achieves type safety and more.

Starting from new Navigation Compose release 2.8.0-alpha08 introduced a Type safe arguments for navigation, Previously we used routes (String) that define a unique id for the corresponding destination. This approach is error-prone and more boilerplate. We need to define its type, default value and nullable or not.

Although the new approach introduced the type safety but it doesn’t replace the core construction of the navigation, it always remains the same.

  • NavHost: Host the current destination of the composable.
  • NavController: Helps to navigate between the destinations, and manages the backstack.
  • NavGraph: Displayes all possible navigation between destinations.

What’s the old approach?

For the basic argument to pass between screens, we need to write this tedious boilerplate code.

val navController = rememberNavController()

NavHost(navController = navController, startDestination = "userScreen") {

composable(route = "userScreen") {
/* ... */
}

composable(
route = "userScreen?name={name}", // route (Error Prone)
arguments = listOf(
navArgument("name") {
type = NavType.StringType
defaultValue = "Mubarak"
nullable = true
}
)
) { backStackEntry ->
val userName = backStackEntry.arguments?.getString("name")
/* ... */
}

}

But starting from this 2.8.0-alpha08 release, we don’t need to write this error-prone code. So far, the current release is 2.8.0-beta02.

Checkout the latest release here releases.

Type safe manner

Let’s implement this with Type safe manner

  1. ) Declare necessary dependency in libs.versions.toml
[versions]

# ...

navigationCompose = "2.8.0-beta02" # currently is in beta
serializationVersion = "1.6.3"

# ...

[libraries]

androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serializationVersion" }

[plugins]

kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

2. ) build.gradle.kts(Module: app)

plugins {
/* ... */

alias(libs.plugins.kotlin.serialization)

/* ... */
}

dependencies {

// Compose Navigation
implementation(libs.androidx.navigation.compose)

// Kotlinx-Serialization
implementation(libs.kotlinx.serialization.json)

}

As, you might ask why kotlinx-serialization library, I will explain it in the moment

For this article i will develop a simple app that helps to demonstrate the new navigation compose.

Application demo

This app contains two destinations MessageScreen and WishScreen MessageScreen contains single textfield and button when user enter their name when click go it displays the user entered text and append the text with Welcome string

In this app we passes Textfiled value to the WishScreen and we implement it with type safe manner.

Define NavHost

val navController = rememberNavController()

NavHost(navController = navController, startDestination = Message) {
composable<Message> {
MessageScreen(text = text, onValueChange = { text = it }) {
navController.navigate(Greeting(name = text))
}
}

composable<Greeting> {
val greeting = it.toRoute<Greeting>()
WishScreen(greeting = greeting)
}
}

In this approach, instead of passing a route as a NavGraphBuilder.composable argument, we use kotlin data class and object

Note: Use object for. If composable that doesn’t require any arguments, if our composable needs an argument use data class and annotate with @Serializable

How it achieve type safety

As I mention previously instead of using route (String) we use data classes or object and annotate this with @Serializable

Serialization is a process of converting one type into another, so that it can be easily transferred.

Navigation compose use this library under the hood to make our nav arguments type safe.

@Serializable
object Message // for MessageScreen

@Serializable
data class Greeting( // for WishScreen
val name: String
// val age:Int? for optional argument we can use nullable type
)

How to get the passed argument value, We easily get that value by NavGraphBuilder.composable() function lambda returns a NavBackStackEntry, we use this to convert it into route by using toRoute() extension function.

composable<Greeting> { -> backStackEntry
val greeting = backStackEntry.toRoute<Greeting>()
WishScreen(greeting = greeting)
}

Here is the, the full code

As, you can see how easy and safe is to use new type safe navigation compose, now this currently is in beta, soonly it becomes in stable channel

Checkout the other code sample on my github repository:

This is all for this simple blog, I will hope you will like this blog

Signing off , Mubarak Native

--

--

Mubarak Native

Mubarak Basha a.k.a (Mubarak Native) Mobile App Developer, Embedded Systems Developer, Tech talks lover,...