DEV Community

Cover image for 🌱 Type-safe navigation in Jetpack Compose - Quick Guide
Sameer Kumar
Sameer Kumar

Posted on

🌱 Type-safe navigation in Jetpack Compose - Quick Guide

Type-safe navigation in Jetpack Compose allows for strong typing and safety when passing arguments between composable screens. Here's a detailed explanation of how it works, along with the provided code and comments:

Dependencies

First, you need to add the necessary dependencies for Kotlin serialization and Jetpack Navigation in Libs.toml and build.gradle files.

libs.version.toml

[versions]
kotlinxSerializationJson = "1.7.1"
kotlinxSerialization = "2.0.20"
navigationCompose = "2.8.0"

[libraries]
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }

[plugins]
jetbrains-kotlin-serialization = { id ="org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinxSerialization"}
Enter fullscreen mode Exit fullscreen mode

build.gradle.kts

plugins {
  alias(libs.plugins.jetbrains.kotlin.serialization) apply false
}
Enter fullscreen mode Exit fullscreen mode

Another build.gradle.kts file:

plugins {
  alias(libs.plugins.jetbrains.kotlin.serialization)
  id("kotlin-parcelize") // needed for non-primitive classes
}

dependencies {
  implementation(libs.androidx.navigation.compose)
  implementation(libs.kotlinx.serialization.json)
}
Enter fullscreen mode Exit fullscreen mode

Data Class Definition

We create a simple data class User to hold user-related data like ID, name, age, and gender.

data class User(
    val id: Int,
    val name: String,
    val age: Int,
    val gender: String
)
Enter fullscreen mode Exit fullscreen mode

Defining Navigation Routes with Serialization

The NavRoutes sealed class defines the possible screens in the app. Each screen can have parameters passed, making navigation type-safe.

@Serializable
sealed class NavRoutes {
    // ListScreen with no arguments
    @Serializable
    data object ListScreen: NavRoutes()

    // UserDetailScreen that accepts parameters for user details
    @Serializable
    data class UserDetailScreen(
        val id: Int,
        val name: String,
        val age: Int,
        val gender: String
    ): NavRoutes()
}
Enter fullscreen mode Exit fullscreen mode

ListScreen (First Screen)

In this screen, we display a list of users from popular anime characters. The items are arranged in a LazyColumn, and each user is clickable, navigating to a detailed screen.

@Composable
fun ListScreen(navController: NavController) {
    // Sample user data (anime characters)
    val myUser = listOf(
        User(1, "Naruto", 14, "Male"),
        User(2, "TenTen", 15, "Female"),
        User(3, "Hinata", 14, "Female"),
        User(4, "Shikamaru", 15, "Male"),
        User(5, "Sakura", 15, "Female"),
        User(6, "Kakashi", 23, "Male")
    )

    // Display the user list in a LazyColumn
    LazyColumn(
        modifier = Modifier.padding(15.dp) // Add padding to the list
    ) {
        // Iterate over the list of users and display each as a SingleUser composable
        items(myUser) {
            SingleUser(navController, name = it.name, id = it.id, age = it.age, gender = it.gender)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

SingleUser

This composable represents an individual user in the list. It’s clickable and navigates to the UserDetailScreen when selected.

@Composable
fun SingleUser(navController: NavController, id: Int, name: String, age: Int, gender: String) {
    Row(
        modifier = Modifier
            .fillMaxSize()
            .padding(5.dp) // Add padding around each user entry
            .clip(RoundedCornerShape(30.dp)) // Rounded corners for a polished look
            .background(Color.LightGray) // Light gray background color
            .clickable {
                // Navigate to UserDetailScreen with the user's details
                navController.navigate(
                    NavRoutes.UserDetailScreen(
                        id = id,
                        age = age,
                        name = name,
                        gender = gender
                    )
                )
            },
        horizontalArrangement = Arrangement.SpaceEvenly, // Space out elements evenly
        verticalAlignment = Alignment.CenterVertically // Align items vertically in the center
    ) {
        // Display user's name, age, and gender with a font size of 15sp
        Text(text = name, fontSize = 15.sp)
        Text(text = age.toString(), fontSize = 15.sp)
        Text(text = gender, fontSize = 15.sp)
    }
}
Enter fullscreen mode Exit fullscreen mode

UserDetailScreen (Second Screen)

This screen displays detailed information about a selected user. The user can also navigate back to the list.

@Composable
fun UserDetailScreen(navController: NavController, name: String, age: Int, gender: String) {
    Column(
        modifier = Modifier.fillMaxSize(), // Take full available space
        horizontalAlignment = Alignment.CenterHorizontally, // Align content horizontally in the center
        verticalArrangement = Arrangement.Center // Align content vertically in the center
    ) {
        // Display user name, age, and gender with some spacing in between
        Text(text = name, fontSize = 20.sp) // Name with larger font size
        Spacer(modifier = Modifier.height(5.dp)) // Spacer between text elements
        Text(text = age.toString(), fontSize = 20.sp)
        Spacer(modifier = Modifier.height(5.dp))
        Text(text = gender, fontSize = 20.sp)
        Spacer(modifier = Modifier.height(5.dp))

        // Back button to return to the previous screen
        Button(onClick = { navController.popBackStack() }) {
            Text(text = "Back")
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

MainActivity Setup

In the NavigationExample activity, we set up navigation using NavHost. It maps the NavRoutes to corresponding composables.

class NavigationExample: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Create a NavController to handle navigation
            val navController = rememberNavController()

            // Define the NavHost which manages the navigation graph
            NavHost(navController = navController, startDestination = NavRoutes.ListScreen) {
                // Composable for ListScreen
                composable<NavRoutes.ListScreen> {
                    ListScreen(navController)
                }
                // Composable for UserDetailScreen with safely passed arguments
                composable<NavRoutes.UserDetailScreen> { navBackStackEntry ->
                    val args = navBackStackEntry.toRoute<NavRoutes.UserDetailScreen>()
                    UserDetailScreen(navController, args.name, args.age, args.gender)
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation

  • Type Safety: NavRoutes uses Kotlin's sealed classes and serialization to safely pass arguments between screens, ensuring type safety. When navigating, you can only pass the expected arguments.
  • Serialization: The @Serializable annotation allows passing complex data types (like objects) through the navigation system in a type-safe way.
  • Back Navigation: On the UserDetailScreen, you can navigate back using navController.popBackStack().

Output

Image description

This setup allows for safe and robust navigation between screens with parameterized data in Jetpack Compose.

Now, it’s time to unleash your new superpowers on the coding world!

<aside>
  <p>
    Thanks for taking the time to read!<br>
    If you found this post helpful,<br>
    I'd appreciate a clap. 😄
  </p>
</aside>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)