Sitemap

Type Safe Navigation Jetpack Compose

4 min readMar 31, 2025

--

What are we going to make :

Navigation Sample App

Prerequities :
Basic Kotlin (OOPS LEVEL)
Basic Compose (OUTLINED TEXT AND BUTTON)
Gradle Basics (Adding dependency)

Get Akshay Sarapure’s stories in your inbox

Join Medium for free to get updates from this writer.

Go to build.gradle.kts(App level) and add these :

//add of this in build.gradle.kts

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "2.1.10" //add this line
}


//add this in dependency section

//Seralization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
//Navigation
val nav_version = "2.8.6"
implementation("androidx.navigation:navigation-compose:$nav_version")

So adding this was easy just add dependenies in mentioned section of build.gradle.kts (app level)

// make a kotlin file and add these 
@Serializable
object ScreenA

@Serializable
data class ScreenB (val data : String)
  • @Serializable is from Kotlin Serialization (kotlinx.serialization library).
  • It enables objects and data classes to be serialized (converted to JSON or another format) and deserialized (converted back).
  • This is especially useful when passing data between screens in navigation
  • Just assume you are saving your screen references thats all

@Composable
fun MainApp(modifier: Modifier = Modifier) {
// Remember the NavController to manage navigation between screens
val navController = rememberNavController()

// Define a navigation host (NavHost) with a start destination (ScreenA)
NavHost(navController = navController, startDestination = ScreenA) {

// Define a composable function for ScreenA
composable<ScreenA> {
// Navigate to ScreenA and pass the NavController to manage navigation
ScreenA(navController = navController)
}

// Define a composable function for ScreenB with data received from a route
composable<ScreenB> { dataFromDataClass ->
// Convert the route data to the expected data class (ScreenB)
val data: ScreenB = dataFromDataClass.toRoute()

// Call the ScreenB composable function and pass extracted data
ScreenBComp(dataFromDataClass = data.data)
}
}
}

NavController helps of manages navigation between Screens . As you noticed that we pass navController object to navigate from one screen to other .
Compsable<ScreenA> and Composable<ScreenB>
helps to map with the data class we created so its basically like mapping screenA (Composable) and ScreenB(Composable) with DataClass that stored refrences to screen .

val data: ScreenB = dataFromDataClass.toRoute()
//this line just gets the data from dataclass we will be sending data from screen a to b

Now you maybe guesing from where is ScreenA(navController = navController) and ScreenBComp(dataFromDataClass = data.data)
coming from let us create these now :


@Composable
fun ScreenA(navController: NavController) {
// Remember a mutable state to store user input
val manualdata = remember { mutableStateOf("") }

// Column to arrange elements vertically
Column(modifier = Modifier.fillMaxSize()) {

// OutlinedTextField to take user input
OutlinedTextField(
value = manualdata.value, // Bind text field value to manualdata state
onValueChange = {
manualdata.value = it // Update state when user types
},
placeholder = { Text("Type data for screen B") } // Show hint text
)

// Button to navigate to ScreenB and pass user input
Button(
onClick = {
navController.navigate(ScreenB(manualdata.value)) // Navigate with data
}//here we sent data in data class
) {
Text("Navigate to Screen 2") // Button text
}
}
}

@Composable
fun ScreenBComp(dataFromDataClass: String) {
// Column layout to display the received data
Column {
Text(dataFromDataClass) // Display the passed data
}
}

📌 ScreenA (The Input Screen)

What happens here?

1️⃣ User types something in a text field.
2️⃣ When they click a button, that text is passed to ScreenB.
Because ScreenB is DataClass Seralizable

📌 ScreenB (The Display Screen)

What happens here?

✅ ScreenBComp receives the data passed from ScreenA and simply displays it.

FULL CODE FOR YOU :

I have comments so that if you are confused you can look forward to read them .



//add of this in build.gradle.kts

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "2.1.10" //add this line
}

//add this in dependency section

//Seralization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
//Navigation
val nav_version = "2.8.6"
implementation("androidx.navigation:navigation-compose:$nav_version")

//Add in NavRoutes,kt kotlin file
@Serializable
object ScreenA

@Serializable
data class ScreenB (val data : String)

//Make a new file kotlin type named MainApp.kt and add this :
@Composable
fun MainApp(modifier: Modifier = Modifier) {
// Remember the NavController to manage navigation between screens
val navController = rememberNavController()

// Define a navigation host (NavHost) with a start destination (ScreenA)
NavHost(navController = navController, startDestination = ScreenA) {

// Define a composable function for ScreenA
composable<ScreenA> {
// Navigate to ScreenA and pass the NavController to manage navigation
ScreenA(navController = navController)
}

// Define a composable function for ScreenB with data received from a route
composable<ScreenB> { dataFromDataClass ->
// Convert the route data to the expected data class (ScreenB)
val data: ScreenB = dataFromDataClass.toRoute()

// Call the ScreenB composable function and pass extracted data
ScreenBComp(dataFromDataClass = data.data)
}
}
}
//Make a new file kotlin type named ScreenA.kt and add this :
@Composable
fun ScreenA(navController: NavController) {
// Remember a mutable state to store user input
val manualdata = remember { mutableStateOf("") }

// Column to arrange elements vertically
Column(modifier = Modifier.fillMaxSize()) {

// OutlinedTextField to take user input
OutlinedTextField(
value = manualdata.value, // Bind text field value to manualdata state
onValueChange = {
manualdata.value = it // Update state when user types
},
placeholder = { Text("Type data for screen B") } // Show hint text
)

// Button to navigate to ScreenB and pass user input
Button(
onClick = {
navController.navigate(ScreenB(manualdata.value)) // Navigate with data
}
) {
Text("Navigate to Screen 2") // Button text
}
}
}
//Make a new file kotlin type named ScreenB.kt and add this :
@Composable
fun ScreenBComp(dataFromDataClass: String) {
// Column layout to display the received data
Column {
Text(dataFromDataClass) // Display the passed data
}
}

Summary:

This blog explains how to implement Jetpack Compose Navigation with Kotlin Serialization to pass data between screens. We set up:

  • Dependencies for serialization and navigation.
  • A NavHost with NavController.
  • ScreenA (User Input) → passes data to ScreenB.
  • ScreenB (Displays Received Data).

By using Kotlin Serialization, we efficiently pass structured data between screens in a type-safe way.

--

--

Akshay Sarapure
Akshay Sarapure

Written by Akshay Sarapure

Android Developer | Jetpack Compose | Kotlin | Firebase | Writing dev guides on clean code, UI, and backend. Let's build great apps together! 🚀