Modern Android Mastery: From Zero to Play Store
Lecture 4

Moving Between Screens: Navigation and Architecture

Modern Android Mastery: From Zero to Play Store

Transcript

SPEAKER_1: Previously, we discussed composables reacting to state, setting the stage for navigation. Now there are multiple screens — how does navigation actually work in Compose? SPEAKER_2: The recommended approach is the Navigation for Compose library. It gives you two core pieces: a NavHost and a NavController. The NavHost is a composable that acts as the container — it swaps visible screens as you navigate. The NavController is the API you call to move between them. SPEAKER_1: So the NavHost is a frame holding whichever screen is active, and the NavController drives changes inside that frame. SPEAKER_2: Exactly. The NavHost defines the navigation graph in Kotlin, mapping route names to destinations. It acts as a table of contents for your app's screens, directing which screen to display when a route is requested. SPEAKER_1: How does passing data between screens work? Suppose someone navigates from a list to a detail screen and needs to carry an item ID. SPEAKER_2: Two approaches. The older one embeds the ID in the route string — like 'detail/{itemId}'. The newer approach, type-safe navigation added in Navigation 2.8, models destinations as Kotlin data structures. You call navigate with a strongly typed object instead of building a string manually. That eliminates runtime errors from mistyped route parameters. SPEAKER_1: That's a real safety upgrade. What about the back button — does the Navigation component handle that automatically? SPEAKER_2: It's automatic. When a NavHost is set up correctly inside an activity, pressing the system back button triggers NavController's popBackStack internally. The key idea is that developers don't intercept back presses manually for standard flows — the component handles it. SPEAKER_1: What about complex structures — like a bottom navigation bar where each tab has its own history? SPEAKER_2: That's where nested graphs come in. You group related destinations — say, an onboarding flow or a single tab — into a nested navigation graph. Options like popUpTo and launchSingleTop let you avoid duplicate stacks when a user taps a tab they're already on. The structure stays modular. SPEAKER_1: So navigation decisions are centralized rather than scattered across screens. That means individual composables shouldn't know they're inside a graph at all. SPEAKER_2: Right. The recommended pattern is to keep navigation logic in a higher-level layer and keep composables unaware of navigation details beyond callbacks. A composable just calls a lambda when something happens — the graph decides where to go next. This ensures composables remain decoupled from navigation logic. SPEAKER_1: Now, where does architecture come in — specifically ViewModel and the layered approach? SPEAKER_2: Android's official architecture guidance recommends at least three logical layers: UI, domain or use-case, and data. The UI layer contains composables and ViewModels, while the data layer manages repositories and data sources. This separation ensures testability and maintainability. SPEAKER_1: For everyone listening, the Repository pattern is probably the least obvious layer. What problem does it actually solve? SPEAKER_2: Think of a Repository as a single source of truth for a particular kind of data. Suppose an app fetches user profiles from a remote API and also caches them locally. Without a Repository, every ViewModel reconciles both sources itself. With one, that logic lives in one place — ViewModels just ask for data and don't care where it comes from. SPEAKER_1: So the ViewModel talks to the Repository, the Repository talks to data sources, and the composable only ever sees the ViewModel's state. Clean separation. SPEAKER_2: And it matters for navigation too. The recommended pattern is to never inject NavController into a ViewModel. Instead, expose navigation events as part of UI state or one-off effects. That way the ViewModel stays testable — no dependency on the navigation framework at all. SPEAKER_1: What about apps that need a URL to open a specific screen — for example, an email link landing someone on a product detail page? SPEAKER_2: That's deep links. The Navigation component maps URIs or intents to specific destinations in the graph. When a supported URL is opened, the system routes the user directly to the corresponding in-app screen. It also supports app links for web-to-app handoff — no custom intent parsing needed. SPEAKER_1: The takeaway for Adrià and everyone building their first multi-screen app seems clear: NavHost and NavController handle routing and back stack, type-safe navigation removes string-based errors, and the layered architecture keeps each concern in its own place. SPEAKER_2: That's it. Composables define screens, ViewModels manage state, Repositories handle data, and the navigation graph controls screen transitions. Get those boundaries right and the app stays maintainable as it grows.