Enhancing Type Safety in Navigation with Jetpack Compose

S M Mohi-Us Sunnat
sunnat629.dev
Published in
3 min readMay 6, 2024

The wait is finally over! After countless requests and anticipation from the developer community, Navigation 2.8.0-alpha08 brings a revolutionary change to Android development by offering type-safe navigation based on Kotlin Serialization. This feature-packed release is a game-changer for those using Jetpack Compose, as it introduces a whole new level of reliability, clarity, and elegance to your navigation graph.

Say goodbye to fragile string-based route arguments and runtime errors. Instead, embrace a world where type safety reigns supreme, empowering you to build dynamic and sophisticated navigation flows with confidence and ease.

Let’s explore how this eagerly awaited feature allows you to create a type-safe navigation graph using the Kotlin DSL, and how it simplifies your development experience.

Prerequisites

Before diving into the new type-safe navigation features, make sure your project includes the necessary dependencies:

1. Navigation Component (2.8.0-alpha08 or later):

implementation("androidx.navigation:navigation-compose:2.8.0-alpha08")

2. Kotlinx Serialization:

implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")

3. Kotlin Serialization Plugin:

Make sure you apply the Kotlin serialization plugin in your `build.gradle.kts` (Project level):

plugins {
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.23"
}

Understanding the Navigation System

The Navigation Component has three main components:

  1. Host — The UI element in your layout that displays the current destination.
  2. Graph — The data structure defining all possible destinations in your app.
  3. Controller — The central coordinator that manages navigation between destinations and saving the back stack.

The Kotlin DSL is one of the ways to build that navigation graph, with three primary methods:

  1. Manually constructing instances of `NavGraph` and adding destinations like Fragment Destinations.
  2. Inflating the graph from a navigation XML file, edited by hand or via the Navigation Editor.
  3. Using the Kotlin DSL to construct the graph directly in your Kotlin code.

Navigation Compose and the Kotlin DSL

Navigation Compose fully embraces the Kotlin DSL for building navigation graphs, moving away from static XML files.

Old Way (String-Based Navigation)

// Navigate to profile details
navController.navigate("profile/{userId}/{name}")
...
composable(
route = "profile/{userId}/{name}",
arguments = listOf(
navArgument("userId") {
type = NavType.IntType
nullable = false
},
navArgument("name") {
type = NavType.StringType
nullable = true
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
val name = backStackEntry.arguments?.getString("name")
ProfileScreen(userId, name)
}

New Way (Type-Safe Navigation with Kotlin DSL)

By leveraging Kotlin’s type-safe properties and Kotlin Serialization, the new navigation system significantly improves the navigation experience.

// Navigate to profile details
navController.navigate(profile)
...
composable<Profile> { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile.id, profile.name)
}

Key Improvements:

  • Typed Arguments: Arguments are encapsulated within a strongly-typed data class.
  • Simplified Navigation Logic: Navigating to the profile screen now only requires passing the `Profile` object.
  • Compile-Time Safety: The `Profile` class ensures that all required arguments are present and correctly typed.

Building the Navigation Graph

Example Code

Define your destination objects using Kotlin’s `@Serializable` annotation:

// Define a home destination that doesn't take any arguments
@Serializable
object Home

// Define a profile destination that takes an ID
@Serializable
data class Profile(val id: String, val name: String)

Build the navigation graph using these destinations:

NavHost(navController, startDestination = Home) {
composable<Home> {
HomeScreen(onNavigateToProfile = { profile ->
navController.navigate(profile)
})
}
composable<Profile> { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile)
}
}

Conclusion

Type-safe navigation in Jetpack Compose makes code more readable, reliable, and maintainable. With strongly-typed routes and compile-time checks, developers can confidently build navigation flows, reducing the likelihood of runtime crashes.

--

--