CS 3180 Mobile Application Development

Week 5 Monday: Configuration Changes and ViewModels

CS 3180 Mobile Application Development

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Today's Topics

  1. Configuration changes: What they are and why they destroy state
  2. Introduction to ViewModels
  3. Building a persistent scorecard with ViewModel
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Part 1: Understanding Configuration Changes

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a Configuration Change?

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a Configuration Change? (continued)

  • Screen rotation (portrait ↔ landscape)
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a Configuration Change? (continued)

  • Screen rotation (portrait ↔ landscape)
  • Dark mode toggle
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a Configuration Change? (continued)

  • Screen rotation (portrait ↔ landscape)
  • Dark mode toggle
  • Language change (system settings)
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a Configuration Change? (continued)

  • Screen rotation (portrait ↔ landscape)
  • Dark mode toggle
  • Language change (system settings)
  • Font size adjustment
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a Configuration Change? (continued)

  • Screen rotation (portrait ↔ landscape)
  • Dark mode toggle
  • Language change (system settings)
  • Font size adjustment
  • Foldable device fold/unfold
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Activity Lifecycle During Configuration Change

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Activity Lifecycle During Configuration Change (continued)

Normal Activity Lifecycle (reference):

Android Activity lifecycle diagram
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Why State is Lost

@Composable
fun BrokenScorecard() {
    var scores by remember { mutableStateOf(List(9) { 0 }) }
    
    Column {
        scores.forEachIndexed { index, score ->
            Text("Hole ${index + 1}: $score strokes")
        }
    }
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

The Problem in Action: Maria's Scenario

What happened:

  1. Maria enters scores for holes 1-5
  2. Phone rings
  3. Maria rotates phone to answer call
  4. Configuration change triggers
  5. Maria looks back at scorecard
  6. All scores reset to 0
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Quick Partial Fix: rememberSaveable

@Composable
fun SurvivingScorecard() {
    var scores by rememberSaveable { 
        mutableStateOf(List(9) { 0 }) 
    }
    
    Column {
        scores.forEachIndexed { index, score ->
            Text("Hole ${index + 1}: $score strokes")
        }
    }
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Part 2: Introduction to ViewModels

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a ViewModel?

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a ViewModel? (continued)

  • Android Architecture Component (part of Jetpack)
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a ViewModel? (continued)

  • Android Architecture Component (part of Jetpack)
  • Survives configuration changes
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a ViewModel? (continued)

  • Android Architecture Component (part of Jetpack)
  • Survives configuration changes
  • Stores and manages UI-related data
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What is a ViewModel? (continued)

  • Android Architecture Component (part of Jetpack)
  • Survives configuration changes
  • Stores and manages UI-related data
  • Cleared when Activity/Fragment finishes (not on rotation)
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

ViewModel Lifecycle

ViewModel lifecycle across configuration changes

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Part 3: Building a Scorecard ViewModel

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Step 1: Define State Class

data class ScorecardState(
    val teamName: String = "",
    val scores: List<Int> = List(9) { 0 },
    val pars: List<Int> = 
        listOf(4, 3, 5, 4, 4, 3, 5, 4, 4),
    val currentHole: Int = 0
) {
    val totalScore: Int get() = scores.sum()
    val totalPar: Int get() = pars.sum()
    val scoreVsPar: Int get() = 
        totalScore - totalPar
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Step 2: Create ViewModel Class

import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

class ScorecardViewModel : ViewModel() {
    private val _state = MutableStateFlow(ScorecardState())
    val state: StateFlow<ScorecardState> = 
        _state.asStateFlow()
    
    // Methods will go here
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Step 3: Add Methods to Update State

class ScorecardViewModel : ViewModel() {
    private val _state = MutableStateFlow(ScorecardState())
    val state: StateFlow<ScorecardState> = 
        _state.asStateFlow()
    
    fun setTeamName(name: String) {
        _state.value = _state.value.copy(teamName = name)
    }
    
    fun updateScore(holeIndex: Int, strokes: Int) {
        val newScores = _state.value.scores.toMutableList()
        newScores[holeIndex] = strokes
        _state.value = _state.value.copy(scores = newScores)
    }
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Step 4: Add Score Manipulation Methods

class ScorecardViewModel : ViewModel() {
    // ... previous code ...
    
    fun incrementScore(holeIndex: Int) {
        val current = _state.value.scores[holeIndex]
        updateScore(holeIndex, current + 1)
    }
    
    fun decrementScore(holeIndex: Int) {
        val current = _state.value.scores[holeIndex]
        if (current > 0) {
            updateScore(holeIndex, current - 1)
        }
    }
    
    fun clearAllScores() {
        _state.value = _state.value.copy(
            scores = List(9) { 0 }
        )
    }
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Step 5: Use ViewModel in Composable

@Composable
fun ScorecardScreen(
    viewModel: ScorecardViewModel = viewModel()
) {
    val state by viewModel.state.collectAsState()
    
    Column(modifier = Modifier.fillMaxSize()) {
        Text(
            "Team: ${state.teamName}",
            style = MaterialTheme.typography.headlineMedium
        )
        
        state.scores.forEachIndexed { index, score ->
            HoleScoreRow(
                holeNumber = index + 1,
                par = state.pars[index],
                strokes = score,
                onIncrement = { 
                    viewModel.incrementScore(index) 
                },
                onDecrement = { 
                    viewModel.decrementScore(index) 
                }
            )
        }
    }
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

StateFlow vs. MutableState

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

StateFlow vs. MutableState (continued)

StateFlow (for ViewModels):

class ViewModel {
    private val _state = MutableStateFlow(ScorecardState())
    val state: StateFlow<ScorecardState> = 
        _state.asStateFlow()
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

StateFlow vs. MutableState (continued)

MutableState (for Compose-only components):

@Composable
fun HoleScoreInput() {
    var strokes by remember { 
        mutableStateOf(0) 
    }
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

ViewModel Best Practices

✅ Store UI state and business logic

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

ViewModel Best Practices (continued)

✅ Store UI state and business logic
✅ Expose immutable state (StateFlow/State)

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

ViewModel Best Practices (continued)

✅ Store UI state and business logic
✅ Expose immutable state (StateFlow/State)
✅ Keep ViewModels Android-independent

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

ViewModel Best Practices (continued)

✅ Store UI state and business logic
✅ Expose immutable state (StateFlow/State)
✅ Keep ViewModels Android-independent
❌ Don't pass Activity/View references

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

ViewModel Best Practices (continued)

✅ Store UI state and business logic
✅ Expose immutable state (StateFlow/State)
✅ Keep ViewModels Android-independent
❌ Don't pass Activity/View references
❌ Don't store lifecycle-dependent objects

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

ViewModel Best Practices (continued)

✅ Store UI state and business logic
✅ Expose immutable state (StateFlow/State)
✅ Keep ViewModels Android-independent
❌ Don't pass Activity/View references
❌ Don't store lifecycle-dependent objects
❌ Don't call database/network directly

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Testing ViewModels

@Test
fun incrementScore_updatesCorrectHole() {
    val viewModel = ScorecardViewModel()
    
    viewModel.incrementScore(0)  // Hole 1
    viewModel.incrementScore(0)
    viewModel.incrementScore(0)
    viewModel.incrementScore(0)  // 4 strokes
    
    assertEquals(4, viewModel.state.value.scores[0])
    assertEquals(4, viewModel.state.value.totalScore)
}
Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

Summary: Maria's Problem is Solved

Before: Rotate device → lose all scores ❌

After: Rotate device → ViewModel survives → scores preserved ✅

Week 5 Monday: Configuration Changes and ViewModels
CS 3180 Mobile Application Development

What We'll Do Wednesday

  • Material Design 3 fundamentals
  • Creating the Hope Foundation brand theme
  • Applying colors, typography, shapes
  • Supporting light and dark themes
Week 5 Monday: Configuration Changes and ViewModels

SPEAKER NOTES: Welcome to Week 5! This lecture addresses a critical real-world problem that beginner developers encounter constantly: configuration changes destroying app state. Hook: "Your phone rotates, and all the data entered by the user disappears. This is the #1 complaint in beginner Android apps." Context: Maria from Hope Foundation discovered this problem in our prototype. She was entering golf scores, got a phone call, rotated her phone to answer it, and when she came back—all scores were reset to zero. We need to fix this problem today using ViewModels. Learning outcome: Students will understand the Android activity lifecycle during configuration changes and implement ViewModels to persist state across these events.

SPEAKER NOTES: We have three main segments today. First, we'll understand the problem deeply by watching it happen live. Then we'll learn the architectural solution: ViewModels. Finally, we'll code a solution that actually works.

SPEAKER NOTES: Let's start with the core problem. When the user rotates their device, something surprising happens: Android destroys the current Activity and recreates it. This process is called a configuration change.

SPEAKER NOTES: Configuration changes are system events that require Android to reload resources. They happen more often than you might think.

SPEAKER NOTES: This is the most common one users encounter. When they rotate their device, Android destroys the Activity and recreates it with new dimensions.

SPEAKER NOTES: Users can change their system theme. This is a configuration change that requires re-evaluating all resources for dark colors.

SPEAKER NOTES: When a user changes the system language, all string resources need to be reloaded.

SPEAKER NOTES: Users can increase system font size for accessibility. This requires layout recalculation.

SPEAKER NOTES: Modern devices like Samsung Galaxy Z Fold handle configuration changes when the device folds or unfolds. All of these events trigger the same lifecycle: Activity destroyed → recreated with new configuration.

SPEAKER NOTES: Let me show you exactly what happens to an Activity when the device rotates.

SPEAKER NOTES: This single reference diagram covers the full Activity lifecycle. We’re using it to explain why configuration changes are so destructive to UI state. When a configuration change happens: 1. The original Activity instance is fully destroyed 2. Android re-evaluates resources for the new configuration 3. A brand new Activity instance is created 4. The lifecycle repeats from onCreate Key point: it’s a new instance, so any state stored in the old Activity is lost unless it’s persisted elsewhere.

SPEAKER NOTES: The `remember` function keeps state alive across recompositions. But a configuration change destroys the entire Activity and Compose runtime. When the Activity is recreated, `remember` creates a brand new MutableState with the default value List(9) { 0 }. All the scores the user entered are gone.

SPEAKER NOTES: This is exactly what happened to Maria when testing our prototype. At a charity golf tournament, this would be devastating. Volunteers would have to re-enter all the scores. This isn't a bug in our code—it's how Android works. We need to use the right architectural component to fix it.

SPEAKER NOTES: `rememberSaveable` is better than `remember`. It saves state to a Bundle (same mechanism as savedInstanceState), so it survives configuration changes. But it has limitations: 1. Only works with primitive types and Parcelable objects 2. Doesn't work well with complex objects like lists of custom classes 3. Doesn't handle business logic or calculations For a robust solution, we need ViewModels.

SPEAKER NOTES: ViewModels are Android Architecture Components designed specifically to handle this problem. They survive configuration changes and follow Android's architectural best practices.

SPEAKER NOTES: A ViewModel is a class that stores and manages UI-related data in a lifecycle-aware way.

SPEAKER NOTES: ViewModels are officially part of Google's recommended Android architecture.

SPEAKER NOTES: Unlike the Activity, ViewModels persist across configuration changes. When the Activity is destroyed and recreated, the same ViewModel instance is reused.

SPEAKER NOTES: ViewModels hold the state that the UI needs: scores, team names, computed values, etc.

SPEAKER NOTES: ViewModels are NOT cleared on configuration changes (rotation), but they ARE cleared when the Activity is actually closed/destroyed by the user or system.

SPEAKER NOTES: This diagram shows the key difference: the Activity is destroyed and recreated twice, but the ViewModel persists through both of those events. When the user finally closes the screen, the ViewModel is cleared. This means any state stored in the ViewModel survives rotation, dark mode changes, etc.

SPEAKER NOTES: Now let's write the code. We'll create a ScorecardViewModel that stores all the state our UI needs.

SPEAKER NOTES: We create a data class to hold all the state our scorecard needs: - teamName: the team playing this round - scores: list of strokes for each hole - pars: the par value for each hole (usually 4, 3, or 5) - currentHole: which hole they're entering data for We also add computed properties: - totalScore: sum of all strokes - totalPar: sum of all pars - scoreVsPar: strokes relative to par (negative is good) Using a data class is cleaner than having separate state variables.

SPEAKER NOTES: We extend ViewModel from androidx.lifecycle. Key pattern: MutableStateFlow for internal state (private), StateFlow for exposed state (public). This prevents outside code from modifying the state directly. They must call our methods. asStateFlow() makes the MutableStateFlow read-only from the outside.

SPEAKER NOTES: We use the `copy()` method from our data class to create a new state object with one field changed. This is the functional/immutable approach. When we update _state.value, Compose automatically detects the change and recomposes the UI.

SPEAKER NOTES: These convenience methods make it easier for the UI to update scores: - incrementScore: add one stroke (typical button press) - decrementScore: remove one stroke (only if > 0) - clearAllScores: reset all holes Each method calls updateScore or modifies _state.value directly.

SPEAKER NOTES: Key line: `val state by viewModel.state.collectAsState()` This collects the StateFlow and converts it to Compose State. When the state changes, this automatically triggers a recomposition. The `viewModel()` function is called with no arguments because Compose remembers the ViewModel instance across recompositions. When the Activity is destroyed and recreated (rotation), the same ViewModel instance is reused, so all the state is preserved.

SPEAKER NOTES: ViewModels use StateFlow instead of MutableState. Let me explain why.

SPEAKER NOTES: StateFlow benefits: - Works with coroutines (important for async operations) - Better testability (easier to test) - Lifecycle-aware collection - Standard in Android architecture - Supports hot flow (always has a value) This is the professional approach used in production apps.

SPEAKER NOTES: MutableState is fine for local UI state that doesn't need to survive configuration changes. But for any state that matters (like golf scores), use ViewModel with StateFlow. Rule of thumb: - Local component state → `remember { mutableStateOf(...) }` - Shared state, business logic → ViewModel with StateFlow

SPEAKER NOTES: The ViewModel should hold any state the UI needs and any logic that calculates derived values.

SPEAKER NOTES: Always expose read-only state. Use private _state and public state pattern.

SPEAKER NOTES: ViewModels should be pure Kotlin. Never pass Context or Activity references into a ViewModel. This makes them testable and prevents memory leaks.

SPEAKER NOTES: If you need Context (for strings, resources), inject them instead of storing references.

SPEAKER NOTES: Don't store objects that are tied to the Activity lifecycle. They'll outlive the Activity and cause memory leaks.

SPEAKER NOTES: For database and network operations, use a Repository pattern (we'll cover this in Week 7). The ViewModel calls the Repository, not the database directly.

SPEAKER NOTES: ViewModels are easy to test because they have no dependencies on the Android framework. You can instantiate them directly and call their methods. This is one of the biggest benefits of the ViewModel architecture: testability.

SPEAKER NOTES: With ViewModels, when Maria's phone rotates, the scorecard data is preserved. When the Activity is recreated, the ViewModel is reused, and the scores are still there. The user experience is seamless—the rotation happens, but the data doesn't disappear. Next session (Wednesday) we'll add Material Design theming to make this scorecard look like the Hope Foundation brand. Friday will be a hands-on workday where you'll implement this in your own project.

SPEAKER NOTES: Wednesday we build on this foundation by adding professional theming. We'll apply the Hope Foundation colors and brand identity to the scorecard we just built.