0
0
Android Kotlinmobile~15 mins

Navigating between composables in Android Kotlin - Deep Dive

Choose your learning style9 modes available
Overview - Navigating between composables
What is it?
Navigating between composables means moving from one screen or UI part to another in an Android app built with Jetpack Compose. Composables are small pieces of UI that can be combined to build screens. Navigation helps users go back and forth between these screens smoothly. It manages which composable is shown and how to switch between them.
Why it matters
Without navigation, an app would be stuck showing only one screen, making it useless for real tasks. Navigation lets users explore different parts of the app, like moving from a list of items to details about one item. It solves the problem of managing multiple screens and user journeys in a clear, organized way. Good navigation improves user experience and app structure.
Where it fits
Before learning navigation, you should understand basic Jetpack Compose concepts like composables and state. After mastering navigation, you can learn about passing data between screens, handling back actions, and advanced navigation patterns like nested navigation or deep linking.
Mental Model
Core Idea
Navigation in Compose is like a map that controls which composable screen is visible and how to move between them.
Think of it like...
Imagine a book with chapters (screens). Navigation is like the table of contents and bookmarks that help you jump to any chapter and come back easily.
┌─────────────┐   navigateTo()   ┌─────────────┐
│ Composable A│ ───────────────▶ │ Composable B│
└─────────────┘                  └─────────────┘
       ▲                              │
       │          navigateBack()      │
       └──────────────────────────────┘
Build-Up - 7 Steps
1
FoundationWhat is a composable function
🤔
Concept: Introduce the basic building block of UI in Jetpack Compose called a composable function.
A composable function is a Kotlin function annotated with @Composable that describes part of the UI. For example: @Composable fun Greeting() { Text("Hello, friend!") } This function shows a simple text on the screen.
Result
You get a reusable UI piece that can be shown anywhere in your app.
Understanding composables is essential because navigation switches between these UI pieces.
2
FoundationSetting up Navigation Controller
🤔
Concept: Learn about NavController, the object that manages navigation state and actions.
NavController keeps track of the current screen and handles moving to other screens. You create it with: val navController = rememberNavController() This controller is passed to the NavHost which connects routes to composables.
Result
You have a navigation manager ready to control which composable is visible.
NavController is the heart of navigation; without it, you can't move between screens.
3
IntermediateDefining Navigation Graph with NavHost
🤔
Concept: Use NavHost to declare all possible composable destinations and their routes.
NavHost links the NavController with composable destinations: NavHost(navController, startDestination = "home") { composable("home") { HomeScreen(navController) } composable("details") { DetailsScreen(navController) } } Each composable is tied to a route string.
Result
The app knows which composables exist and how to reach them by route names.
The navigation graph organizes your app's screens and routes in one place.
4
IntermediateNavigating between composables using routes
🤔Before reading on: do you think navigation uses function calls or route strings? Commit to your answer.
Concept: Navigate by telling NavController to go to a route string representing a composable screen.
To move from one screen to another, call: navController.navigate("details") This tells the NavController to show the composable tied to "details" route.
Result
The UI switches from the current composable to the target composable smoothly.
Navigation uses route names, not direct function calls, allowing flexible and decoupled screen switching.
5
IntermediateHandling back navigation in Compose
🤔Before reading on: does pressing back always close the app or go to the previous screen? Commit your guess.
Concept: Back navigation returns to the previous composable in the navigation stack instead of closing the app immediately.
NavController keeps a stack of visited screens. Calling: navController.popBackStack() moves back to the previous composable. The system back button also triggers this by default.
Result
Users can go back step-by-step through screens they visited.
Back navigation is managed by the navigation stack, not by closing the app, improving user control.
6
AdvancedPassing data between composables via navigation
🤔Before reading on: do you think you can pass complex objects directly via navigation routes? Commit your answer.
Concept: You can pass simple data like strings or numbers as route parameters to the next composable.
Define route with arguments: composable("details/{itemId}") { backStackEntry -> val itemId = backStackEntry.arguments?.getString("itemId") DetailsScreen(itemId) } Navigate with: navController.navigate("details/42") This passes '42' as itemId.
Result
The target composable receives data to show specific content.
Passing data via routes lets screens communicate without tight coupling.
7
ExpertAdvanced navigation: nested graphs and deep links
🤔Before reading on: do you think navigation graphs can be split into smaller parts? Commit your answer.
Concept: Navigation graphs can be nested to organize complex apps, and deep links allow external URLs to open specific screens.
You can create nested NavHost inside a composable to group related screens: navigation(startDestination = "profile", route = "user") { composable("profile") { ProfileScreen() } composable("settings") { SettingsScreen() } } Deep links let apps open screens from outside: composable("details/{itemId}", deepLinks = listOf(navDeepLink { uriPattern = "app://details/{itemId}" })) { ... } This supports opening details screen from a URL.
Result
Apps become modular and support external navigation sources.
Understanding nested graphs and deep links enables building scalable and integrated apps.
Under the Hood
Underneath, NavController maintains a stack of destinations representing composable screens. When navigate() is called, it pushes a new destination onto the stack and recomposes the UI to show the new composable. popBackStack() removes the top destination, returning to the previous one. The NavHost listens to NavController's state and displays the current composable accordingly. Route strings are parsed to match destinations and extract parameters.
Why designed this way?
This design separates UI from navigation logic, making apps modular and easier to maintain. Using a stack mimics natural user navigation and system back behavior. Routes as strings allow flexible, decoupled screen definitions and support deep linking. The architecture follows Android's existing navigation principles but adapts them to Compose's declarative UI model.
┌───────────────┐
│ NavController │
├───────────────┤
│ Stack:        │
│ [Home, Details]│
└───────┬───────┘
        │ updates
        ▼
┌───────────────┐
│   NavHost     │
│ (UI Listener) │
└───────┬───────┘
        │ shows
        ▼
┌───────────────┐
│ Composable UI │
│ (Current Screen)│
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does calling navController.navigate("screen") recreate the composable every time? Commit yes or no.
Common Belief:Navigation recreates the composable from scratch every time you navigate to it.
Tap to reveal reality
Reality:Compose reuses composables when possible and preserves state if managed correctly; navigation changes the visible composable but doesn't always recreate it fully.
Why it matters:Thinking navigation always recreates UI can lead to unnecessary state resets and poor user experience.
Quick: Can you pass any Kotlin object directly as a navigation argument? Commit yes or no.
Common Belief:You can pass any Kotlin object directly through navigation arguments.
Tap to reveal reality
Reality:Navigation arguments must be simple types (strings, ints) or Parcelable/Serializable; complex objects need other methods like ViewModel sharing.
Why it matters:Trying to pass unsupported objects causes crashes or data loss.
Quick: Does pressing the system back button always close the app? Commit yes or no.
Common Belief:Pressing back always closes the app immediately.
Tap to reveal reality
Reality:Back navigates to the previous composable in the stack; the app closes only when the stack is empty.
Why it matters:Misunderstanding back behavior can confuse users and cause navigation bugs.
Quick: Is navigation in Compose tightly coupled to UI code? Commit yes or no.
Common Belief:Navigation logic must be written inside composable UI functions.
Tap to reveal reality
Reality:Navigation can be separated from UI code using ViewModels or navigation handlers for cleaner architecture.
Why it matters:Mixing navigation and UI code reduces maintainability and testability.
Expert Zone
1
NavController's back stack can be manipulated programmatically to customize navigation flows beyond simple forward/back actions.
2
Using rememberNavController() inside composables preserves navigation state across recompositions and configuration changes.
3
Deep links support both app-internal navigation and external URLs, enabling seamless integration with other apps and web.
When NOT to use
For very simple apps with only one screen or static UI, full navigation setup may be unnecessary. Alternatives include manual state management or single composable screens. Also, for complex multi-module apps, consider advanced navigation libraries or patterns like multi-activity navigation.
Production Patterns
In production, navigation graphs are split into nested graphs per feature for modularity. Shared ViewModels handle data passing between composables. Deep links and conditional navigation handle onboarding flows and authentication states. Navigation actions are often wrapped in helper functions to centralize logic.
Connections
State management in Jetpack Compose
Navigation controls which composable is visible, while state management controls what data the composable shows.
Understanding navigation alongside state helps build dynamic apps where screens update based on user actions and data changes.
Web URL routing
Navigation routes in Compose are similar to URL paths in web apps that determine which page to show.
Knowing web routing concepts clarifies how navigation routes work as identifiers for screens and how parameters are passed.
Human cognitive map
Navigation in apps mimics how humans mentally map places and paths to move between locations.
Recognizing this connection helps design intuitive navigation flows that match user expectations.
Common Pitfalls
#1Navigating without a NavController or forgetting to pass it to composables.
Wrong approach:composable("home") { HomeScreen() } // HomeScreen tries navController.navigate but navController is null
Correct approach:val navController = rememberNavController() NavHost(navController, startDestination = "home") { composable("home") { HomeScreen(navController) } }
Root cause:Not creating or passing NavController means navigation commands have no effect or cause crashes.
#2Passing complex objects directly in navigation routes.
Wrong approach:navController.navigate("details/$myComplexObject")
Correct approach:Pass only simple IDs in routes, then load complex objects inside the destination composable using ViewModel or repository.
Root cause:Navigation routes only support simple types; complex objects must be handled separately.
#3Ignoring back stack and calling navigate() repeatedly without popBackStack().
Wrong approach:navController.navigate("details") navController.navigate("details") // Stack grows infinitely
Correct approach:navController.navigate("details") { launchSingleTop = true restoreState = true }
Root cause:Not managing back stack leads to multiple copies of the same screen and confusing back behavior.
Key Takeaways
Navigation between composables controls which screen the user sees and how they move through the app.
NavController and NavHost work together to manage navigation state and display the correct composable.
Routes are string identifiers that link navigation actions to composable destinations and can carry simple data.
Back navigation uses a stack to remember previous screens, improving user experience.
Advanced navigation includes nested graphs and deep links for modular and integrated apps.