This lesson demonstrates Jetpack Compose Navigation with practical examples of:
- Setting up NavHost
- Defining composable routes
- Passing arguments between screens
- Back navigation
Here's what you'll build in this lesson:
Screens from left to right:
- Home Screen - Starting point with navigation options
- Profile Screen - Simple navigation without arguments
- Details Screen - Navigation with arguments (Item ID: 1, Name: Kotlin)
- Details Screen - Another example (Item ID: 3, Name: Navigation)
app/src/main/java/com/example/androidkotlinlesson4/
├── MainActivity.kt # Entry point with NavController setup
├── navigation/
│ ├── Screen.kt # Sealed class defining all routes
│ └── NavigationGraph.kt # NavHost configuration
└── screens/
├── HomeScreen.kt # Start destination with navigation options
├── ProfileScreen.kt # Simple screen without arguments
└── DetailsScreen.kt # Screen receiving navigation arguments
Added to gradle/libs.versions.toml:
navigationCompose = "2.8.0"
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }A sealed class defines all navigation destinations:
sealed class Screen(val route: String) {
object Home : Screen("home")
object Profile : Screen("profile")
object Details : Screen("details/{itemId}/{itemName}") {
fun createRoute(itemId: Int, itemName: String) = "details/$itemId/$itemName"
}
}Why sealed class?
- Type-safe route definitions
- Compile-time route checking
- Easy to maintain and refactor
The NavHost defines all routes and their composables:
@Composable
fun NavigationGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Screen.Home.route
) {
// Define all composable destinations here
}
}Key components:
- NavHost: Container for navigation destinations
- startDestination: Initial screen to display
- composable(): Defines each navigable screen
Navigate to ProfileScreen without passing data:
// In NavigationGraph
composable(route = Screen.Profile.route) {
ProfileScreen(
onNavigateBack = {
navController.popBackStack()
}
)
}
// In HomeScreen - trigger navigation
Button(onClick = { navController.navigate(Screen.Profile.route) }) {
Text("Go to Profile")
}Pass multiple arguments to DetailsScreen:
Define route with parameters:
object Details : Screen("details/{itemId}/{itemName}") {
fun createRoute(itemId: Int, itemName: String) = "details/$itemId/$itemName"
}Configure composable with arguments:
composable(
route = Screen.Details.route,
arguments = listOf(
navArgument("itemId") { type = NavType.IntType },
navArgument("itemName") { type = NavType.StringType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
val itemName = backStackEntry.arguments?.getString("itemName") ?: ""
DetailsScreen(
itemId = itemId,
itemName = itemName,
onNavigateBack = { navController.popBackStack() }
)
}Navigate with arguments:
navController.navigate(Screen.Details.createRoute(id, name))- Purpose: Starting destination with navigation examples
- Features:
- Button to navigate to Profile (no args)
- List of items that navigate to Details (with args)
- Demonstrates both navigation patterns
- Purpose: Simple destination demonstrating back navigation
- Features:
- Top app bar with back button
- User profile information display
- Back button in content area
- Purpose: Demonstrates receiving and using navigation arguments
- Features:
- Displays received itemId and itemName
- Shows how arguments are passed through routes
- Educational content about argument passing
navController.navigate(route)navController.popBackStack()navController.navigate("details/$itemId/$itemName")
// OR using helper function
navController.navigate(Screen.Details.createRoute(itemId, itemName))- IntType: Integer values
- StringType: String values
- BoolType: Boolean values
- FloatType: Float values
- LongType: Long values
- Use Sealed Classes for Routes: Type-safe and maintainable
- Create Helper Functions: For routes with arguments
- Define Arguments Explicitly: Specify type for each argument
- Handle Null Cases: Use default values when extracting arguments
- Separate Navigation Logic: Keep NavHost in separate file
- Pass Callbacks: Use lambda parameters for navigation actions
-
Sync Gradle: Ensure navigation-compose dependency is downloaded
- File → Sync Project with Gradle Files
-
Build and Run: Deploy to emulator or device
- Run → Run 'app'
-
Test Navigation:
- Click "Go to Profile" for simple navigation
- Click any item in the list to navigate with arguments
- Use back button or "Go Back" to return
Button(onClick = { navController.navigate(Screen.Profile.route) })Card(onClick = {
navController.navigate(Screen.Details.createRoute(id, name))
})IconButton(onClick = { navController.popBackStack() })navController.popBackStack(Screen.Home.route, inclusive = false)After completing this lesson, you will understand:
✅ NavHost and NavController Setup
- How to create and configure NavController using
rememberNavController() - How to set up NavHost with a start destination
- Understanding the navigation lifecycle
✅ Navigation Routes
- Defining routes using sealed classes for type safety
- Creating simple routes without parameters
- Creating routes with multiple parameters
- Using helper functions to build parameterized routes
✅ Navigate Between Screens
- Using
navController.navigate()to move forward - Passing data through navigation arguments
- Understanding the navigation back stack
✅ Argument Passing
- Defining argument types (Int, String, Boolean, etc.)
- Extracting arguments from BackStackEntry
- Handling optional arguments with default values
- Best practices for type-safe argument passing
✅ Back Navigation
- Using
navController.popBackStack()to go back - Implementing back buttons in TopAppBar
- Understanding when to use
popBackStack()vsnavigateUp()
✅ Code Organization
- Separating navigation logic from UI components
- Using callbacks for navigation actions
- Keeping screens independent and reusable
- Structuring a scalable navigation architecture
🎯 Navigation is State Management
- NavController maintains the navigation state
- Each screen is a destination in the back stack
- Arguments are part of the saved state
🎯 Type Safety Matters
- Use sealed classes for routes to prevent typos
- Define argument types explicitly
- Leverage Kotlin's type system for safer navigation
🎯 Separation of Concerns
- Screens should not know about NavController directly
- Pass navigation callbacks as lambda parameters
- Keep navigation logic in the NavigationGraph
🎯 User Experience
- Always provide a way to go back
- Use consistent navigation patterns
- Handle the system back button automatically
Try these exercises to reinforce your learning:
-
Add a Settings Screen
- Create a new route in
Screen.kt - Add a composable destination in
NavigationGraph.kt - Add a button from HomeScreen to navigate to Settings
- Create a new route in
-
Pass More Arguments
- Modify DetailsScreen to accept a description parameter
- Update the route definition and argument parsing
- Pass the description from HomeScreen
-
Implement Navigate Up
- Try using
navController.navigateUp()instead ofpopBackStack() - Understand the difference between the two methods
- Try using
-
Add Validation
- Add validation for arguments (e.g., itemId must be positive)
- Handle invalid arguments gracefully
- Show error messages when needed
-
Experiment with Back Stack
- Use
popBackStack(route, inclusive)to skip screens - Try navigating with
launchSingleTop = true - Understand how to clear the back stack
- Use
❌ Problem: Unresolved reference errors for navigation imports ✅ Solution: Sync Gradle files after adding dependencies
❌ Problem: Arguments are null in destination screen ✅ Solution: Check argument names match exactly in route definition and extraction
❌ Problem: App crashes when navigating with arguments ✅ Solution: Ensure argument types are defined correctly in the composable
❌ Problem: Back button doesn't work as expected
✅ Solution: Use navController.popBackStack() or let the system handle it automatically
- Navigation Compose Documentation
- NavHost API Reference
- Navigation Best Practices
- Compose Navigation Codelab
In this lesson, you've learned how to implement navigation in Jetpack Compose using the Navigation Component. You can now:
- Set up a navigation graph with multiple destinations
- Navigate between screens with and without arguments
- Extract and use navigation arguments
- Implement proper back navigation
- Organize your navigation code following best practices
Navigation is a fundamental part of Android app development, and mastering these concepts will help you build more complex, multi-screen applications with confidence!



