Blog Infos
Author
Published
Topics
, , , ,
Published
This image was generated with the assistance of AI
Introduction

Jetpack Compose, Android’s modern UI toolkit, has introduced a revolutionary approach to navigation with its latest update. The release of Navigation 2.8.0-alpha08 introduces a type-safe navigation system powered by Kotlin Serialization. This new method offers a robust solution to the challenges of managing navigation routes and passing data between composables, ensuring compile-time safety and reducing runtime errors.

1. The Evolution of Navigation in Compose

Before the introduction of type-safe navigation, Jetpack Compose developers relied heavily on string-based route definitions to manage navigation between screens. This approach required manually constructing routes, embedding parameters within strings, and parsing these strings in destination composables. While functional, this method introduced several challenges:

Example of String-Based Navigation:
// Navigating to a profile screen with an ID
navController.navigate("profile/123")

In this example, "profile/123" is a string that encodes the route and its parameter. However, this simplicity comes with several drawbacks:

Challenges with String-Based Navigation:

1. Lack of Type Safety: Parameters are passed as strings, requiring manual extraction and conversion. If a parameter was expected to be an Int, but a String was passed, this mismatch wouldn’t be caught until runtime, potentially causing crashes.
kotlin

// Extracting the ID from the route string
val profileId = backStackEntry.arguments?.getString("id")?.toIntOrNull() ?: 0

If the conversion fails (e.g., due to a non-numeric string), the app could behave unexpectedly or crash.

2. Manual String Construction: Developers had to manually concatenate and interpolate strings to create routes, leading to errors if route formatting was incorrect.

// Incorrectly formatted route 
val userId = "123" navController.navigate("profile/userId") // Should be "$userId"

Such issues were common, especially in larger apps with many parameters, making the navigation logic error-prone and harder to maintain.

3. Runtime Errors: Since the navigation routes were constructed as strings, errors in route names or parameter types weren’t caught until the app was running, making debugging difficult.

// Navigation failure due to typo navController.navigate("profiles/123") // Incorrect route name

This error would only surface at runtime, making it harder to trace and fix issues.

4. Limited Scalability: As apps grew in complexity, managing multiple routes and parameters through strings became cumbersome, with the risk of inconsistencies increasing. This was especially challenging when maintaining navigation across different modules or features within an app.

In summary, while string-based navigation worked, it lacked the robustness required for larger, more complex applications. The need for a more reliable and maintainable approach led to the development of type-safe navigation, which we’ll explore in the next section.

2. Leveraging Kotlin Serialization for Type-Safe Navigation

Kotlin Serialization is at the core of this system, allowing developers to define destinations using @Serializable classes. These classes encapsulate the arguments required by each destination, ensuring that the correct types are used throughout the navigation process.

Example:
@Serializable
object Home

@Serializable
data class Profile(val id: String)

This method allows developers to define navigation routes without relying on strings, thereby minimizing the potential for errors and improving code clarity.

Defining a Type-Safe Navigation Graph

With Kotlin Serialization, defining your navigation graph is straightforward. Instead of manually constructing route strings, you define your destinations directly in Kotlin:

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

This approach eliminates the need for route strings and manual argument handling, making the navigation code easier to manage and less error-prone.

Simplifying Navigation Logic

The type-safe approach allows for a more streamlined navigation process. Instead of constructing routes with string interpolation, you simply pass the destination class and its arguments:

navController.navigate(Profile(id = "123"))

This ensures that navigation is consistent and type-safe, catching potential errors at compile time.

Incremental Adoption

The new type-safe navigation system in Jetpack Compose is designed to be adopted incrementally, allowing developers to transition gradually from string-based navigation without overhauling the entire codebase at once. Here’s how you can approach it:

Step 1: Start with One Destination

Begin by converting a single screen’s navigation to the type-safe approach using @Serializable classes.

Old Approach:
// String-based navigation to Profile screen
navController.navigate("profile/123")
New Approach:
@Serializable
data class Profile(val id: String)
// Type-safe navigation to Profile screen
navController.navigate(Profile(id = "123"))
Step 2: Incrementally Convert Additional Destinations

As you become comfortable with the new system, convert additional destinations one by one. This way, you can test and verify each transition, ensuring that your app remains stable.

Example:
@Serializable
data class Settings(val theme: String)
// Convert the Settings screen next
navController.navigate(Settings(theme = "Dark"))
Step 3: Mix and Match During Transition

During the transition, you can mix the old string-based navigation with the new type-safe approach. This allows you to continue developing and maintaining your app without being forced into a full refactor.

Example of Mixed Navigation:
// Old-style navigation
navController.navigate("home")

// New type-safe navigation
navController.navigate(Profile(id = "456"))

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

Step 4: Full Transition

Once you’ve converted all destinations to use the type-safe approach, your navigation code will be more maintainable, less error-prone, and fully type-safe, providing compile-time guarantees across your entire navigation structure.

This incremental approach ensures that you can adopt the new type-safe navigation system at your own pace, minimizing risks and allowing for smoother development.

Handling Complex Data Types

If your application requires passing complex data structures, Kotlin Serialization can handle this seamlessly. For instance, custom data types like Parcelable can be used as fields in your @Serializable classes, ensuring that even complex data is passed safely and efficiently.

@Parcelable
data class SearchParameters(val query: String, val filters: List<String>)

@Serializable
data class Search(val parameters: SearchParameters)
val SearchParametersType = object : NavType<SearchParameters>(isNullableAllowed = false) {
    // Implementation details...
}
composable<Search>(typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)) { backStackEntry ->
    val searchParameters = backStackEntry.toRoute<Search>().parameters
    SearchScreen(searchParameters)
}
Dependencies

Last but not least! To use this new type-safe navigation system, you’ll need to add the following dependencies to your project:

  1. Jetpack Navigation Component:
implementation "androidx.navigation:navigation-compose:2.8.0-alpha08"

2. Kotlin Serialization:

implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1"

3. Parcelable Support(if needed):

implementation "org.jetbrains.kotlin:kotlin-parcelize-runtime:1.9.0"

These dependencies will enable you to implement type-safe navigation in your Jetpack Compose applications, leveraging the latest features of Kotlin Serialization.

3. Conclusion

The introduction of type-safe navigation in Jetpack Compose with Kotlin Serialization marks a significant advancement in Android development. By adopting this approach, developers can create more reliable, maintainable, and intuitive navigation flows in their apps. This feature is likely to become the standard for Compose navigation, offering developers a powerful tool to build better, safer applications.

Dobri Kostadinov
Android Consultant | Trainer
Email me | Follow me on LinkedIn | Follow me on Medium | Buy me a coffee

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
Menu