0
0
Android Kotlinmobile~20 mins

State in ViewModel in Android Kotlin - Mini App: Build & Ship

Choose your learning style9 modes available
Build: Counter Screen
A simple screen that shows a number and a button to increase it. The number is stored in a ViewModel to keep state across screen rotations.
Target UI
-------------------
|   Counter: 0    |
|                 |
|   [Increase]    |
-------------------
Create a ViewModel to hold the counter state as LiveData or StateFlow
Display the current counter value on the screen
Add a button labeled 'Increase' that increments the counter in the ViewModel
Ensure the counter value survives screen rotations
Starter Code
Android Kotlin
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel

class CounterViewModel : ViewModel() {
    // TODO: Add counter state here
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: android.os.Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CounterScreen()
        }
    }
}

@Composable
fun CounterScreen(counterViewModel: CounterViewModel = viewModel()) {
    // TODO: Display counter and button
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
Android Kotlin
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

class CounterViewModel : ViewModel() {
    private val _counter = MutableStateFlow(0)
    val counter: StateFlow<Int> = _counter.asStateFlow()

    fun increment() {
        _counter.value += 1
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: android.os.Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CounterScreen()
        }
    }
}

@Composable
fun CounterScreen(counterViewModel: CounterViewModel = viewModel()) {
    val count by counterViewModel.counter.collectAsState()

    Surface(modifier = androidx.compose.ui.Modifier.fillMaxSize()) {
        Column(
            horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
            verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center,
            modifier = androidx.compose.ui.Modifier.fillMaxSize()
        ) {
            Text(text = "Counter: $count", style = MaterialTheme.typography.headlineMedium)
            Spacer(modifier = androidx.compose.ui.Modifier.height(16.dp))
            Button(onClick = { counterViewModel.increment() }) {
                Text("Increase")
            }
        }
    }
}

We use a MutableStateFlow in the CounterViewModel to hold the counter state. This allows the state to survive configuration changes like screen rotations because ViewModel lives longer than the Activity.

The counter is exposed as a read-only StateFlow. The increment() function updates the counter value.

In the CounterScreen composable, we collect the counter state as Compose state using collectAsState(). This makes the UI automatically update when the counter changes.

The UI shows the current counter value in a Text and a button labeled "Increase" that calls increment() in the ViewModel.

Final Result
Completed Screen
-------------------
|   Counter: 0    |
|                 |
|   [Increase]    |
-------------------
When user taps 'Increase', the number increments by 1
The displayed number updates immediately
If the device is rotated, the number stays the same
Stretch Goal
Add a 'Reset' button that sets the counter back to zero
💡 Hint
Add a reset() function in ViewModel that sets _counter.value = 0 and add a Button in the UI calling it