ViewModel & UI State Quiz
ARCHITECTURE › Components
An app is rotated, then later killed by the OS while in the background and restored. Which mechanism preserves state across BOTH events?
- A plain ViewModel that stores state in a MutableStateFlow
- A companion-object singleton that holds the state across screens
- A ViewModel with state kept in SavedStateHandle
- rememberSaveable used only inside the Composable UI tree
Answer: A ViewModel with state kept in SavedStateHandle
The ViewModel handles the rotation (config change), and SavedStateHandle persists the value to the saved-state Bundle so it survives system-initiated process death and restoration.
Why is injecting an Activity Context into a ViewModel considered a bug?
- A ViewModel can outlive the Activity, so it leaks the destroyed one
- ViewModels run on a background thread, where a Context is unavailable
- Context cannot be passed through a ViewModelProvider.Factory at all
- The compiler forbids any constructor parameters on a ViewModel class
Answer: A ViewModel can outlive the Activity, so it leaks the destroyed one
Because a ViewModel survives configuration changes, it can outlive the Activity that created it; retaining the Activity Context leaks that destroyed instance.
What is the correct way to expose screen state from a ViewModel for unidirectional data flow?
- Expose the MutableStateFlow directly as public, so the UI can edit it
- Expose a public var that the UI reassigns whenever the screen changes
- Expose a MutableLiveData and let the UI call setValue on it directly
- Keep a private MutableStateFlow and expose it with asStateFlow()
Answer: Keep a private MutableStateFlow and expose it with asStateFlow()
A private MutableStateFlow exposed read-only via asStateFlow() lets the UI observe but not mutate state, preserving unidirectional data flow.
What happens to coroutines launched in viewModelScope when the ViewModel is cleared?
- They keep running until they complete naturally
- They are cancelled automatically when onCleared() runs
- They are paused and resumed on the next ViewModel instance
- They must be cancelled manually in onCleared() or they leak
Answer: They are cancelled automatically when onCleared() runs
viewModelScope is cancelled as part of clearing the ViewModel, so all coroutines launched in it are automatically cancelled without manual cleanup.
Which scenario does NOT require you to write a custom ViewModelProvider.Factory?
- The ViewModel constructor takes a UserRepository the framework cannot provide
- The ViewModel needs a manually constructed AnalyticsClient at creation time
- The ViewModel is annotated @HiltViewModel with an @Inject constructor
- The ViewModel requires a dependency passed via a custom CreationExtras.Key
Answer: The ViewModel is annotated @HiltViewModel with an @Inject constructor
Hilt generates the ViewModel factory at compile time for @HiltViewModel classes, so you do not write one; the other cases need a factory to supply dependencies.
Inside a viewModelFactory { initializer { ... } } block, how do you obtain a SavedStateHandle for the ViewModel?
- Call SavedStateHandle() to construct a fresh empty instance
- Read it from this[VIEW_MODEL_KEY]
- Cast the Application from APPLICATION_KEY to a SavedStateHandle
- Call createSavedStateHandle()
Answer: Call createSavedStateHandle()
createSavedStateHandle() builds a SavedStateHandle from the CreationExtras inside the initializer; constructing one directly would not be wired to saved state.
Which value can be stored in SavedStateHandle so it reliably survives process death?
- A Parcelable data class instance
- A live android.view.View reference kept from the UI
- An open OkHttp Call object still tied to the request
- A lambda that captures the Activity instance
Answer: A Parcelable data class instance
SavedStateHandle serializes to a Bundle, so values must be Bundle-compatible such as primitives, Parcelable, or Serializable; Views, network calls, and lambdas are not persistable.
When is a ViewModel's onCleared() actually called?
- After every configuration change, such as a full device screen rotation
- Whenever the entire app process is pushed into the background
- Only when its ViewModelStoreOwner is permanently destroyed for good
- Each time a coroutine launched inside viewModelScope finally completes
Answer: Only when its ViewModelStoreOwner is permanently destroyed for good
onCleared() runs when the ViewModelStoreOwner is finished for good, not on config changes (the whole point is the ViewModel is retained across rotation), and not merely when backgrounded.
Two Fragments hosted in the same Activity need to read and update the same screen state. What is the idiomatic way to share one ViewModel instance between them?
- Each Fragment uses by viewModels() for its own instance, then syncs by callback
- Both Fragments use by activityViewModels() for one Activity-scoped instance
- Pass the ViewModel through the Fragment constructor so both can reuse it directly
- Store the ViewModel in a static field and let both Fragments read that shared copy
Answer: Both Fragments use by activityViewModels() for one Activity-scoped instance
by activityViewModels() scopes the ViewModel to the Activity's ViewModelStore, so both Fragments obtain the same instance and share state; per-Fragment viewModels() would create separate instances.
You want a value stored in SavedStateHandle to drive Compose UI and recompose whenever it changes. Which API is the right choice?
- Call savedStateHandle.get<T>(key) repeatedly in a polling loop to check changes
- Keep a separate MutableStateFlow and manually push every set into it yourself
- Read the value once in init and store it in a val for the rest of the ViewModel
- Use savedStateHandle.getStateFlow(key, default) to get an observable StateFlow
Answer: Use savedStateHandle.getStateFlow(key, default) to get an observable StateFlow
getStateFlow(key, default) exposes the saved value as an observable StateFlow that re-emits whenever the key is updated, so the UI reacts automatically while still surviving process death.
In Compose, why is collectAsStateWithLifecycle() generally preferred over collectAsState() for a ViewModel's StateFlow?
- It collects only while lifecycle is at least STARTED, then pauses
- It skips recomposition entirely, so the screen always renders faster
- It converts the StateFlow into LiveData automatically under the hood
- It forces the upstream flow to run on a background dispatcher always
Answer: It collects only while lifecycle is at least STARTED, then pauses
collectAsStateWithLifecycle() is lifecycle-aware: it stops collecting when the host drops below STARTED (e.g., screen backgrounded) and resumes later, avoiding wasted work; plain collectAsState() collects regardless of lifecycle.
A ViewModel genuinely needs a Context (for example to read string resources). What is the safe way to obtain one?
- Pass the Activity into the constructor and clear it in onCleared()
- Weakly hold the current Activity and recheck it before each resource use
- The Application context via AndroidViewModel or APPLICATION_KEY
- Use reflection to call ActivityThread.currentApplication() at runtime
Answer: The Application context via AndroidViewModel or APPLICATION_KEY
The Application context lives as long as the process and never leaks an Activity, so AndroidViewModel or APPLICATION_KEY is the correct source; any Activity-bound context risks leaking the destroyed Activity.
A ViewModel needs to emit a one-time 'navigate to the next screen' signal. Why is putting it in a StateFlow<UiState> problematic, and what fits better?
- StateFlow is fine; just set the navigate flag once and never clear it
- StateFlow replays its value, re-firing on rotation; a Channel fits better
- One-time events only deliver when stored inside a SavedStateHandle
- Only LiveData wrapped in an Event wrapper class can deliver one-time signals
Answer: StateFlow replays its value, re-firing on rotation; a Channel fits better
Because StateFlow always replays its current value to new collectors, a recomposition or rotation re-triggers the event; a Channel consumed as a Flow (or a SharedFlow with replay 0) delivers each event exactly once.
A repository exposes a cold Flow<List<Item>>. What is the idiomatic way to surface it from a ViewModel as a hot StateFlow that stops upstream collection shortly after the UI goes away?
- Collect it in init and copy each item into a MutableStateFlow manually
- Return the cold Flow directly from the ViewModel and let the UI manage it
- Call flow.asStateFlow() on the repository flow to expose it as hot state
- Use flow.stateIn(viewModelScope, WhileSubscribed(5000), initialValue)
Answer: Use flow.stateIn(viewModelScope, WhileSubscribed(5000), initialValue)
stateIn with SharingStarted.WhileSubscribed(5000) converts a cold flow into a hot StateFlow scoped to viewModelScope, keeping upstream collection alive only while there are subscribers (plus a short grace window for config changes).