/** * State, Navigation & ViewModel — Practical Exercises * Mobile Application Development | SUZA | Semester II 2025/2026 * * Copy into an Android Studio project with: * implementation("androidx.navigation:navigation-compose:2.7.7") * implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") */ import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow // 1. A ViewModel with counter + reset class CounterViewModel : ViewModel() { private val _count = MutableStateFlow(0) val count: StateFlow = _count.asStateFlow() fun inc() { _count.value++ } fun reset() { _count.value = 0 } } @Composable fun CounterScreen1(vm: CounterViewModel = viewModel()) { val count by vm.count.collectAsState() Column(Modifier.padding(16.dp)) { Text("Count: $count") Button(onClick = vm::inc) { Text("Increment") } Button(onClick = vm::reset) { Text("Reset") } } } // 2. Navigation — two screens with argument passing @Composable fun App2() { val nav = rememberNavController() NavHost(nav, startDestination = "home") { composable("home") { HomeScreen2(nav) } composable( route = "greet/{name}", arguments = listOf(navArgument("name") { type = NavType.StringType }) ) { entry -> GreetScreen2( name = entry.arguments?.getString("name") ?: "Guest", onBack = { nav.popBackStack() } ) } } } @Composable fun HomeScreen2(nav: NavHostController) { var name by remember { mutableStateOf("") } Column(Modifier.padding(16.dp)) { OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Name") }) Button( onClick = { nav.navigate("greet/${name.ifBlank { "Guest" }}") }, enabled = name.isNotBlank() ) { Text("Greet") } } } @Composable fun GreetScreen2(name: String, onBack: () -> Unit) { Column(Modifier.padding(16.dp)) { Text("Hello, $name!") Button(onClick = onBack) { Text("Back") } } } // 3. Shared ViewModel across screens — Cupcake-style UDF data class OrderUiState( val quantity: Int = 0, val flavor: String = "", val pickupDate: String = "", val price: Double = 0.0 ) class OrderViewModel : ViewModel() { private val _state = MutableStateFlow(OrderUiState()) val state: StateFlow = _state.asStateFlow() fun setQuantity(q: Int) { _state.value = _state.value.copy(quantity = q, price = q * 2.00) } fun setFlavor(f: String) { _state.value = _state.value.copy(flavor = f) } fun setDate(d: String) { _state.value = _state.value.copy(pickupDate = d) } fun reset() { _state.value = OrderUiState() } } // 4. rememberSaveable vs remember @Composable fun SaveableDemo4() { var a by remember { mutableStateOf(0) } // lost on rotation var b by rememberSaveable { mutableStateOf(0) } // survives rotation Column(Modifier.padding(16.dp)) { Button(onClick = { a++; b++ }) { Text("A=$a B=$b (click then rotate)") } } } // 5. Form state hoisting — LoginForm data class LoginState(val email: String = "", val password: String = "") { val isValid: Boolean get() = "@" in email && password.length >= 6 } @Composable fun LoginForm5(state: LoginState, onChange: (LoginState) -> Unit, onSubmit: () -> Unit) { Column(Modifier.padding(16.dp)) { OutlinedTextField( value = state.email, onValueChange = { onChange(state.copy(email = it)) }, label = { Text("Email") } ) OutlinedTextField( value = state.password, onValueChange = { onChange(state.copy(password = it)) }, label = { Text("Password") } ) Button(onClick = onSubmit, enabled = state.isValid) { Text("Login") } } } @Preview @Composable fun LoginForm5Preview() { var s by remember { mutableStateOf(LoginState()) } LoginForm5(state = s, onChange = { s = it }, onSubmit = { }) }