CS 3180 Mobile Application Development

Week 7 Wednesday: Grids + Navigation Demo

CS 3180 Mobile Application Development

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

This is the Task

Build a Tournament Hub Application

A golf tournament app with two screens:

  1. Hub Screen - A grid dashboard showing holes 1-18, each clickable
  2. Hole Detail Screen - Shows details about the selected hole with back navigation

Key Features:

  • Grid layout for quick access
  • Navigation with arguments (hole number)
  • Back button for return navigation
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Two Approaches to Navigation

We'll explore two different ways to implement navigation:

Method 1: String-Based Routes (Simple)

  • Routes defined as string constants
  • Direct string navigation
  • Quick to set up
  • Routes defined as sealed types
  • Compile-time safety
  • Better organized for larger apps

We'll build the same app both ways!

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Method 1: String-Based Navigation

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

What Are the Steps? (Method 1)

Build Order:

  1. Define navigation routes (strings)
  2. Set up NavHost with NavController
  3. Create the Hub screen with Scaffold
  4. Build the grid layout with LazyVerticalGrid
  5. Design individual hole tiles
  6. Build the detail screen with back button
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 1: Define Navigation Routes

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 1: Define Navigation Routes

object Routes {
    const val HOME = "home"
    const val HOLE_DETAIL = "hole/{holeNumber}"
}

Key Points:

  • Centralized constants avoid typos
  • Route parameter in curly braces {holeNumber}
  • Simple string-based approach
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 2: Set Up NavHost with NavController

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 2: Set Up NavHost with NavController

@Composable
fun GolfApp() {
    val navController = rememberNavController()

    NavHost(
        navController = navController,
        startDestination = Routes.HOME
    ) {
        composable(Routes.HOME) { HubScreen(navController) }
        composable(
            route = Routes.HOLE_DETAIL,
            arguments = listOf(navArgument("holeNumber") { type = NavType.IntType })
        ) { entry ->
            val holeNumber = entry.arguments?.getInt("holeNumber") ?: 1
            HoleDetailScreen(holeNumber, navController)
        }
    }
}
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 2: Set Up NavHost with NavController

Key Points:

  • Define routes inline in NavHost
  • Manual argument extraction with getInt()
  • Pass NavController to screens
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 3: Create the Hub Screen with Scaffold

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 3: Create the Hub Screen with Scaffold

@Composable
fun HubScreen(navController: NavController) {
    val holes = (1..18).toList()

    Scaffold(
        topBar = { TopAppBar(title = { Text("Tournament Hub") }) }
    ) { padding ->
        HoleGrid(
            holes = holes,
            onHoleClick = { hole ->
                navController.navigate("hole/$hole")
            },
            modifier = Modifier.padding(padding)
        )
    }
}

Key Points:

  • Generates hole numbers 1-18
  • Navigation string built dynamically: "hole/$hole"
  • NavController used directly
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 4: Build the Grid Layout with LazyVerticalGrid

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 4: Build the Grid Layout with LazyVerticalGrid

@Composable
fun HoleGrid(
    holes: List<Int>,
    onHoleClick: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
    LazyVerticalGrid(
        modifier = modifier,
        columns = GridCells.Fixed(3),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp),
        horizontalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(holes, key = { it }) { hole ->
            HoleTile(hole, onHoleClick)
        }
    }
}

Key Points:

  • 3 columns for good mobile layout
  • Spacing keeps tiles comfortable to tap
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 5: Design Individual Hole Tiles

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 5: Design Individual Hole Tiles

@Composable
fun HoleTile(hole: Int, onHoleClick: (Int) -> Unit) {
    Surface(
        shape = RoundedCornerShape(12.dp),
        tonalElevation = 2.dp,
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onHoleClick(hole) }
    ) {
        Box(
            modifier = Modifier.padding(20.dp),
            contentAlignment = Alignment.Center
        ) {
            Text("Hole $hole", style = MaterialTheme.typography.titleMedium)
        }
    }
}

Key Points:

  • .clickable modifier triggers navigation
  • Surface provides elevation and shape
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 6: Build the Detail Screen with Back Button

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 6: Build the Detail Screen with Back Button

@Composable
fun HoleDetailScreen(holeNumber: Int, navController: NavController) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Hole $holeNumber") },
                navigationIcon = {
                    IconButton(onClick = { navController.popBackStack() }) {
                        Icon(Icons.Default.ArrowBack, contentDescription = "Back")
                    }
                }
            )
        }
    ) { padding ->
        Column(
            modifier = Modifier
                .padding(padding)
                .padding(16.dp)
        ) {
            Text("Par: 4", style = MaterialTheme.typography.titleMedium)
            Text("Notes: Water hazard left", style = MaterialTheme.typography.bodyMedium)
        }
    }
}
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 6: Build the Detail Screen with Back Button

Key Points:

  • popBackStack() returns to previous screen
  • Hole number displayed in title bar
  • NavController dependency in screen
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Method 1 Complete! ✅

What We Built:

  • String-based route navigation
  • Grid dashboard with 18 holes
  • Detail screen with back navigation

Pros:

  • ✅ Simple and straightforward
  • ✅ Easy to learn
  • ✅ Quick to set up

Cons:

  • ❌ No compile-time safety (typos possible)
  • ❌ NavController in screens (harder to test)
  • ❌ Manual argument parsing
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Method 2: Type-Safe Navigation Graph

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

What Are the Steps? (Method 2)

Build Order:

  1. Define type-safe navigation routes
  2. Create a navigation graph builder
  3. Set up NavHost with the graph
  4. Create the Hub screen with callbacks
  5. Build the grid layout (same as Method 1)
  6. Design individual hole tiles (same as Method 1)
  7. Build the detail screen with callbacks
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 1: Define Type-Safe Navigation Routes

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 1: Define Type-Safe Navigation Routes

import kotlinx.serialization.Serializable

@Serializable
sealed interface Screen {
    @Serializable
    data object Home : Screen

    @Serializable
    data class HoleDetail(val holeNumber: Int) : Screen
}

Key Points:

  • @Serializable enables type-safe navigation
  • Sealed interface ensures exhaustive when expressions
  • Data class carries parameters (holeNumber)
  • Data object for routes with no parameters
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 2: Create a Navigation Graph Builder

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 2: Create a Navigation Graph Builder

fun NavGraphBuilder.golfNavGraph(navController: NavController) {
    composable<Screen.Home> {
        HubScreen(
            onNavigateToHole = { holeNumber ->
                navController.navigate(Screen.HoleDetail(holeNumber))
            }
        )
    }

    composable<Screen.HoleDetail> { backStackEntry ->
        val holeDetail = backStackEntry.toRoute<Screen.HoleDetail>()
        HoleDetailScreen(
            holeNumber = holeDetail.holeNumber,
            onNavigateBack = { navController.popBackStack() }
        )
    }
}
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 2: Create a Navigation Graph Builder

Key Points:

  • composable<Screen.Home> uses type instead of string
  • toRoute<>() extracts typed arguments automatically
  • Lambda callbacks keep screens decoupled from NavController
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 3: Set Up NavHost with the Graph

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 3: Set Up NavHost with the Graph

@Composable
fun GolfApp() {
    val navController = rememberNavController()

    NavHost(
        navController = navController,
        startDestination = Screen.Home
    ) {
        golfNavGraph(navController)
    }
}

Key Points:

  • startDestination is now a typed route object (not string)
  • Graph builder keeps NavHost clean
  • All routes defined in one place
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 4: Create the Hub Screen with Callbacks

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 4: Create the Hub Screen with Callbacks

@Composable
fun HubScreen(onNavigateToHole: (Int) -> Unit) {
    val holes = (1..18).toList()

    Scaffold(
        topBar = { TopAppBar(title = { Text("Tournament Hub") }) }
    ) { padding ->
        HoleGrid(
            holes = holes,
            onHoleClick = onNavigateToHole,
            modifier = Modifier.padding(padding)
        )
    }
}

Key Points:

  • Callback instead of NavController dependency
  • Easier to test and preview
  • Screen doesn't know about navigation infrastructure
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 5: Build the Grid Layout

This step is identical to Method 1!

@Composable
fun HoleGrid(
    holes: List<Int>,
    onHoleClick: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
    LazyVerticalGrid(
        modifier = modifier,
        columns = GridCells.Fixed(3),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp),
        horizontalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(holes, key = { it }) { hole ->
            HoleTile(hole, onHoleClick)
        }
    }
}
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 6: Design Individual Hole Tiles

This step is identical to Method 1!

@Composable
fun HoleTile(hole: Int, onHoleClick: (Int) -> Unit) {
    Surface(
        shape = RoundedCornerShape(12.dp),
        tonalElevation = 2.dp,
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onHoleClick(hole) }
    ) {
        Box(
            modifier = Modifier.padding(20.dp),
            contentAlignment = Alignment.Center
        ) {
            Text("Hole $hole", style = MaterialTheme.typography.titleMedium)
        }
    }
}
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 7: Build the Detail Screen with Callbacks

Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 7: Build the Detail Screen with Callbacks

@Composable
fun HoleDetailScreen(
    holeNumber: Int,
    onNavigateBack: () -> Unit
) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Hole $holeNumber") },
                navigationIcon = {
                    IconButton(onClick = onNavigateBack) {
                        Icon(Icons.Default.ArrowBack, contentDescription = "Back")
                    }
                }
            )
        }
    ) { padding ->
        Column(
            modifier = Modifier
                .padding(padding)
                .padding(16.dp)
        ) {
            Text("Par: 4", style = MaterialTheme.typography.titleMedium)
            Text("Notes: Water hazard left", style = MaterialTheme.typography.bodyMedium)
        }
    }
}
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Step 7: Build the Detail Screen with Callbacks

Key Points:

  • Back navigation through callback (not NavController)
  • No navigation dependency
  • Easy to preview and test in isolation
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Method 2 Complete! ✅

What We Built:

  • Type-safe navigation with sealed routes
  • Organized navigation graph
  • Grid dashboard with 18 holes
  • Decoupled screens using callbacks

Pros:

  • ✅ Compile-time safety (no typos possible)
  • ✅ Screens are testable (no NavController)
  • ✅ Better code organization
  • ✅ Automatic argument parsing
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Method 2 Complete! ✅

Cons:

  • ❌ Slightly more setup code
  • ❌ Need to learn serialization
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Comparison: Method 1 vs Method 2

Feature Method 1 (Strings) Method 2 (Type-Safe)
Setup Complexity Simple Moderate
Type Safety ❌ None ✅ Compile-time
Testability ❌ Hard (NavController) ✅ Easy (callbacks)
Scalability ❌ Gets messy ✅ Stays organized
Error Detection Runtime Compile-time
Best For Learning, tiny apps Production apps
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Quick Review

Today We Learned:

  • Grid layouts with LazyVerticalGrid
  • Two approaches to navigation in Compose
  • String-based routes (simple but limited)
  • Type-safe navigation graphs (robust and scalable)

Key Takeaways:

  • Both methods achieve the same result
  • Type-safe navigation prevents bugs at compile-time
  • Decoupling screens from NavController improves testability
  • Choose the right tool for your project size
Week 7 Wednesday: Grids + Navigation Demo
CS 3180 Mobile Application Development

Quick Review

Next Steps:

  • Apply Method 2 to your Hope Foundation app
  • Use navigation graphs for multi-screen flows
Week 7 Wednesday: Grids + Navigation Demo

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.