Jetpack Compose Navigation for Beginners: Move Between Screens
Connect screens with NavHost and NavController, pass data between screens with routes, go back with popBackStack, and see what to learn next.
Real apps have more than one screen. You tap a product, and a details page opens. You press back, and you return. In Part 8 of the Android for Beginners series, you'll learn navigation in Jetpack Compose โ how to move between screens and send data from one screen to another.
We'll continue the fruit list from Part 7: Show a List with LazyColumn.
How Navigation Works (The Simple Picture)
Think of your app like a building:
- Each screen is a room.
- Each room has a name, called a route (like
"home"or"details"). - The NavController is your guide. You tell it, "take me to details", and it walks you there.
- The NavHost is the map of the building. It lists every room.
When you open a new screen, Android puts it on top of a stack, like plates. Pressing back takes the top plate off, and you see the screen underneath.
Step 1: Add the Navigation Library
Navigation isn't built in, so we add it. Open app/build.gradle.kts (the one inside the app folder) and add this line inside dependencies { ... }:
implementation("androidx.navigation:navigation-compose:2.9.0")
Click Sync Now at the top of the editor. Android Studio will download the library.
Tip: Newer versions come out often. Android Studio may highlight the line and offer to update it. Updating is fine.
Step 2: Make the Two Screens
Screen 1: the fruit list. Update the FruitListScreen from Part 7 so it reports a tap instead of showing a Toast:
@Composable
fun FruitListScreen(
onFruitClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(fruits, key = { it.name }) { fruit ->
FruitRow(fruit = fruit, onClick = { onFruitClick(fruit.name) })
}
}
}
Screen 2: the details page.
@Composable
fun FruitDetailsScreen(
fruitName: String,
onBack: () -> Unit,
modifier: Modifier = Modifier
) {
val fruit = fruits.find { it.name == fruitName }
Column(
modifier = modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
if (fruit == null) {
Text("Fruit not found ๐")
} else {
Text(text = fruit.emoji, fontSize = 96.sp)
Text(text = fruit.name, fontSize = 32.sp, fontWeight = FontWeight.Bold)
Text(text = "Price: เงณ${fruit.price} per kg", fontSize = 18.sp)
}
Spacer(modifier = Modifier.height(24.dp))
Button(onClick = onBack) {
Text("โฌ
Back to list")
}
}
}
Notice something smart: the screens don't know about navigation. They just say "a fruit was clicked" or "back was pressed". This keeps them simple and easy to reuse.
Step 3: Connect Everything with NavHost
@Composable
fun FruitApp(modifier: Modifier = Modifier) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home",
modifier = modifier
) {
composable("home") {
FruitListScreen(
onFruitClick = { name ->
navController.navigate("details/$name")
}
)
}
composable("details/{fruitName}") { backStackEntry ->
val name = backStackEntry.arguments?.getString("fruitName") ?: ""
FruitDetailsScreen(
fruitName = name,
onBack = { navController.popBackStack() }
)
}
}
}
Finally, show FruitApp(modifier = Modifier.padding(innerPadding)) inside setContent and press Run.
Tap a fruit โ the details page opens. Tap Back (or use the phone's back gesture) โ you return to the list. ๐
Let's Understand It
rememberNavController()creates your guide.startDestination = "home"โ the first screen the user sees.composable("home") { ... }โ registers a screen with its route name."details/{fruitName}"โ the{fruitName}part is a blank to fill in. It lets us pass data.navController.navigate("details/Mango")โ go to the details screen and fill the blank withMango.backStackEntry.arguments?.getString("fruitName")โ read the value on the new screen.navController.popBackStack()โ go back one screen (take the top plate off).
Good habit: Only pass small things like an ID or a name between screens, never whole objects. The new screen can look up the rest, just like we did with
fruits.find { ... }.
๐ Look How Far You've Come!
In just 8 parts, you learned:
- What Android development is
- How to install Android Studio
- How to build and run your first app
- Kotlin basics
- How to build screens with Compose
- Buttons, clicks and state
- Scrolling lists with LazyColumn
- Navigation between screens (this post)
You now have a real, working app with a list, a details page and navigation. That's a big deal. Be proud! ๐ช
What's Coming Next in the Series
The series continues! In the next parts, we will turn this fruit app into a complete, real-world app:
- Part 9: ViewModel โ keep your app's data safe
- Part 10: Scaffold, top app bar and bottom navigation
- Part 11: Material 3 themes, colors and dark mode
- Part 12: Load images from the internet with Coil
- Part 13: Get real data from the internet with Retrofit
- Part 14: Loading, error and retry screens
- Part 15: Save data on the phone with Room database
- Part 16: Save settings with DataStore
- Part 17: Debugging with Logcat and breakpoints
- Part 18: Publish your app on Google Play
Want to read ahead on advanced topics? Try Jetpack Compose: State Management Best Practices and Kotlin Coroutines: From Basics to Advanced Patterns.
Frequently Asked Questions
Can I have more than two screens?
Yes! Just add more composable("route") { ... } blocks inside the NavHost.
How do I stop the user from going back to a screen (like a login page)?
Use navController.navigate("home") { popUpTo("login") { inclusive = true } }. This removes the login screen from the stack.
What's the best way to practice now? Build a small app of your own idea: a to-do list, a notes app, or a recipe book. Mixing everything you learned is the fastest way to grow.
Quick Recap
- A NavHost lists your screens, and a NavController moves between them.
- Each screen has a route name, like
"home". - Pass small data with route blanks like
"details/{fruitName}". popBackStack()goes back.
Great work today! Next, let's make our app smarter and keep its data safe in Part 9: ViewModel for Beginners. ๐