SPEAKER NOTES:
Today's demo builds a Tournament Hub and shows TWO different navigation approaches.
SPEAKER NOTES:
Give students 5-10 minutes to think about how they would approach this. What components do they need? What navigation structure? This active thinking primes them for learning.
SPEAKER NOTES:
Method 1 is simpler and good for learning. Method 2 is what you'll use in production apps. Let's see both approaches.
SPEAKER NOTES:
This is the straightforward approach. Good for small apps and learning the basics.
SPEAKER NOTES:
This is the traditional approach. We'll reveal each step one at a time.
SPEAKER NOTES:
Define routes first so the app has a clear map of screens. Ask: Why do we centralize route constants?
SPEAKER NOTES:
The curly braces indicate a route parameter that will be filled in at navigation time.
SPEAKER NOTES:
The NavHost is the container for all our screens. Ask students: What's the purpose of startDestination?
SPEAKER NOTES:
We define two destinations: home (hub) and detail with a typed Int argument. The argument is extracted from the backstack entry.
SPEAKER NOTES:
The Hub screen uses Scaffold for structure. Ask: Why pass navController to child components?
SPEAKER NOTES:
The navigate call builds the route dynamically. For hole 5, it becomes "hole/5".
SPEAKER NOTES:
LazyVerticalGrid is our scrollable grid container. Ask: Why use Fixed(3) columns?
SPEAKER NOTES:
ContentPadding adds breathing room around the entire grid. The spacedBy values create gaps between tiles.
SPEAKER NOTES:
Each tile is a clickable Surface. Ask: What makes it tappable?
SPEAKER NOTES:
When clicked, the tile calls onHoleClick with its hole number, which triggers navigation.
SPEAKER NOTES:
The detail screen receives the hole number as a parameter. Ask: How does the back button work?
SPEAKER NOTES:
The back arrow in the navigation icon area provides familiar navigation UX. PopBackStack removes the current screen from the navigation stack.
SPEAKER NOTES:
This works great for small apps. But for larger apps, we need something more robust. That's where Method 2 comes in.
SPEAKER NOTES:
Now let's rebuild the same app using the modern, type-safe approach. You'll see how this prevents bugs and makes code cleaner.
SPEAKER NOTES:
Steps 5-6 are identical to Method 1. The difference is in how we define routes and handle navigation.
SPEAKER NOTES:
We'll use a sealed interface to represent all possible routes. Ask: Why use sealed types instead of strings?
SPEAKER NOTES:
This approach provides compile-time safety. You can't navigate to a route that doesn't exist, and you can't forget to pass required parameters.
SPEAKER NOTES:
The navigation graph function encapsulates all route definitions. Ask: Why separate the graph from the NavHost setup?
SPEAKER NOTES:
The graph builder is an extension function on NavGraphBuilder. This keeps navigation logic organized and reusable.
SPEAKER NOTES:
The main app composable connects NavController with our graph. Ask: How is this cleaner than Method 1?
SPEAKER NOTES:
Compare this to Method 1 where all routes were defined inline. This is much cleaner and the graph can be tested independently.
SPEAKER NOTES:
The Hub screen receives a navigation callback instead of NavController directly. Ask: Why is this better for testing?
SPEAKER NOTES:
The screen doesn't know about navigation. It just reports user actions via callbacks. This is better separation of concerns.
SPEAKER NOTES:
The grid component is the same in both methods. Navigation method doesn't affect UI components.
SPEAKER NOTES:
Again, the tile component is identical. Only the navigation wiring changes between methods.
SPEAKER NOTES:
The detail screen receives the hole number and a back callback. Ask: How does this improve testability?
SPEAKER NOTES:
The screen is completely decoupled from navigation infrastructure. You could test it by passing a mock lambda.
SPEAKER NOTES:
The screen is completely decoupled from navigation infrastructure. You could test it by passing a mock lambda.
SPEAKER NOTES:
The cons are minimal. For any real app, Method 2 is the way to go.
SPEAKER NOTES:
Use Method 1 to learn the basics. Use Method 2 for real projects. The Hope Foundation app should use Method 2.
The good: Type safety eliminates entire classes of runtime errors. No more getString() null checks or manual type conversion.
The questionable: You're coupling your navigation graph to kotlinx.serialization. If you need custom serialization logic or have complex argument types, you're fighting the framework. Also, the toRoute<T>() extension adds magic—it's not immediately obvious where deserialization failures surface.
T
he bleeding edge concern: This pattern assumes navigation arguments are simple data. Once you need to pass complex objects, callbacks, or state holders, you're back toViewModel sharing or SavedStateHandle gymnastics anyway.
SPEAKER NOTES:
Connect this to the Hope Foundation app: they'll use similar patterns for navigating between animal lists and detail screens. The type-safe approach prevents navigation bugs at compile time.