Bird
Raised Fist0
Android Kotlinmobile~20 mins

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

Choose your learning style10 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

Practice

(1/5)
1. What is the main purpose of using ViewModel in Android development?
easy
A. To replace the Activity lifecycle completely
B. To handle user input events directly
C. To store and manage UI-related data safely during configuration changes
D. To perform network requests on the main thread

Solution

  1. Step 1: Understand ViewModel role and compare options

    ViewModel is designed to hold UI data and survive configuration changes like screen rotations. Only To store and manage UI-related data safely during configuration changes correctly describes this purpose; others describe unrelated or incorrect roles.
  2. Final Answer:

    To store and manage UI-related data safely during configuration changes -> Option C
  3. Quick Check:

    ViewModel stores UI data safely [OK]
Hint: ViewModel keeps UI data safe during screen rotations [OK]
Common Mistakes:
  • Thinking ViewModel handles UI events directly
  • Assuming ViewModel replaces Activity lifecycle
  • Using ViewModel for network calls on main thread
2. Which of the following is the correct way to declare a mutable state in a ViewModel using LiveData?
easy
A. var data = MutableLiveData() as LiveData<String>
B. val data = LiveData<String>()
C. var data = LiveData<String>()
D. val data = MutableLiveData<String>()

Solution

  1. Step 1: Identify mutable LiveData declaration and syntax

    MutableLiveData allows changing the value; LiveData alone is read-only. val data = MutableLiveData<String>() correctly declares a mutable LiveData with type String.
  2. Final Answer:

    val data = MutableLiveData<String>() -> Option D
  3. Quick Check:

    MutableLiveData is mutable LiveData [OK]
Hint: MutableLiveData allows data changes; LiveData is read-only [OK]
Common Mistakes:
  • Using LiveData() directly (it's abstract)
  • Declaring mutable LiveData as var without MutableLiveData
  • Casting MutableLiveData to LiveData incorrectly
3. Given this ViewModel code snippet:
class MyViewModel : ViewModel() {
  private val _count = MutableLiveData(0)
  val count: LiveData<Int> = _count

  fun increment() {
    _count.value = (_count.value ?: 0) + 1
  }
}

What will be the value of count after calling increment() twice starting from initial state?
medium
A. 2
B. 1
C. 0
D. null

Solution

  1. Step 1: Trace execution from initial value

    _count starts at 0 as MutableLiveData(0). Each increment adds 1: first call sets 1, second call sets 2.
  2. Final Answer:

    2 -> Option A
  3. Quick Check:

    0 + 1 + 1 = 2 [OK]
Hint: Each increment adds 1 to current LiveData value [OK]
Common Mistakes:
  • Forgetting to unwrap nullable value with ?: 0
  • Assuming LiveData updates automatically without setting value
  • Confusing private _count with public count
4. Identify the error in this ViewModel code:
class SampleViewModel : ViewModel() {
  val data = MutableLiveData<String>()

  fun updateData(newValue: String) {
    data.value = newValue
  }

  fun getData(): String {
    return data.value
  }
}
medium
A. getData() returns nullable String but declared as non-null
B. MutableLiveData cannot hold String values
C. data.value should be updated using postValue() only
D. ViewModel cannot have functions

Solution

  1. Step 1: Analyze getData() return type mismatch

    data.value is nullable String?, but getData() returns non-null String. Returning nullable as non-null causes compile error or runtime crash.
  2. Final Answer:

    getData() returns nullable String but declared as non-null -> Option A
  3. Quick Check:

    LiveData.value is nullable [OK]
Hint: LiveData.value is nullable; match return types [OK]
Common Mistakes:
  • Ignoring nullable type of LiveData.value
  • Thinking postValue() is mandatory for all updates
  • Believing ViewModel cannot have functions
5. You want to expose a read-only LiveData from your ViewModel while keeping the MutableLiveData private. Which code snippet correctly implements this pattern?
hard
A. private val _state = MutableLiveData<Int>() val state: MutableLiveData<Int> = _state
B. private val _state = MutableLiveData<Int>() val state: LiveData<Int> get() = _state
C. val state = MutableLiveData<Int>()
D. val _state = LiveData<Int>() val state: MutableLiveData<Int> = _state

Solution

  1. Step 1: Identify correct encapsulation pattern

    MutableLiveData should be private; expose only LiveData publicly. private val _state = MutableLiveData<Int>() val state: LiveData<Int> get() = _state declares private MutableLiveData and public LiveData getter correctly.
  2. Final Answer:

    private val _state = MutableLiveData<Int>()\nval state: LiveData<Int> get() = _state -> Option B
  3. Quick Check:

    Expose LiveData, keep MutableLiveData private [OK]
Hint: Expose LiveData getter, keep MutableLiveData private [OK]
Common Mistakes:
  • Exposing MutableLiveData publicly
  • Declaring LiveData as mutable
  • Using LiveData constructor directly (abstract class)
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard