MVVM & Unidirectional Data Flow Quiz
ARCHITECTURE › Patterns
In unidirectional data flow, which statement correctly describes the directions?
- State flows up to the ViewModel; events flow down to the View
- State flows down to the View; events flow up to the ViewModel
- Both state and events flow down from the ViewModel to the View
- Both state and events flow up from the View to the ViewModel
Answer: State flows down to the View; events flow up to the ViewModel
UDF means state is produced by the ViewModel and flows down to the UI, while user events flow up to the ViewModel, which processes them and emits new state.
Why should a ViewModel avoid holding a reference to an Activity, Fragment, or View?
- Because the Android build will refuse to compile a ViewModel that imports View
- Because ViewModels may only hold primitive types, never object references
- It outlives them across config changes, risking leaks and hurting testability
- Because a held View reference makes StateFlow silently stop emitting
Answer: It outlives them across config changes, risking leaks and hurting testability
A ViewModel survives configuration changes and outlives the View, so holding a View reference leaks it; staying framework-free also keeps the ViewModel JVM-testable.
What is the recommended way to model the complete state of a single screen?
- A single immutable data class exposed as one observable stream
- Several independent MutableLiveData fields the View updates directly
- A mutable global singleton shared across all screens
- Public var properties on the Activity
Answer: A single immutable data class exposed as one observable stream
Combining related state into one immutable data class exposed as a single stream gives a consistent single source of truth and fewer inconsistencies than scattered mutable fields.
Which approach best represents loading, success, and error states for a screen?
- Throw exceptions from the ViewModel and let the View catch them
- Store loading and error booleans as static fields on the Application class
- Use Android's Context to look up the current screen state on demand
- Encode them in the immutable UI state, via flags or a sealed interface
Answer: Encode them in the immutable UI state, via flags or a sealed interface
Loading, error, and empty conditions are part of what the user should see, so they belong inside the immutable UI state (flags or a sealed hierarchy), keeping a single source of truth.
In Jetpack Compose, how is 'state down, events up' typically implemented for a stateless Composable?
- It reads a global mutable value and writes changes back into it directly
- It takes state as parameters and emits callback lambdas to the caller
- It stores a ViewModel field and mutates business state from UI code
- It fetches data from the repository itself instead of receiving state
Answer: It takes state as parameters and emits callback lambdas to the caller
State hoisting passes state down as parameters and lifts events up via callbacks, so the Composable stays stateless and the ViewModel owns the state.
Which API is recommended for collecting a ViewModel's StateFlow in the UI so collection respects lifecycle?
- observeForever on the underlying value
- runBlocking inside onResume
- collectAsStateWithLifecycle()
- Reading flow.value once in onCreate
Answer: collectAsStateWithLifecycle()
collectAsStateWithLifecycle() collects only while the lifecycle is at least STARTED, avoiding wasted work and updates while the UI is in the background.
According to the single-source-of-truth principle, who should mutate the UI state a ViewModel exposes?
- Only the data owner, the ViewModel itself, should mutate it
- Any Composable that renders part of the state should update it
- The Activity during configuration changes should change it
- Whichever repository read the data last should mutate it
Answer: Only the data owner, the ViewModel itself, should mutate it
Only the source or owner of the data should update it; allowing the View or others to mutate it creates multiple sources of truth and inconsistencies.
What is the idiomatic way for a ViewModel to expose StateFlow-based UI state so the UI cannot mutate it?
- Expose a public MutableStateFlow so the UI can post updates directly to it
- Expose the state as a public var and reassign it on each UI change
- Hold a private MutableStateFlow, exposed read-only via asStateFlow()
- Expose a Channel that the UI both sends events to and receives from
Answer: Hold a private MutableStateFlow, exposed read-only via asStateFlow()
Backing the public stream with a private MutableStateFlow and exposing it as a read-only StateFlow enforces that only the ViewModel mutates state, preserving the single source of truth.
When turning a cold Flow from the data layer into a StateFlow in the ViewModel, which operator configuration stops upstream work shortly after the UI goes to the background yet still survives a configuration change?
- stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue)
- stateIn(viewModelScope, SharingStarted.Eagerly, initialValue) kept always active
- Collecting the flow in the Activity's onResume and caching the result
- Wrapping the flow in runBlocking and caching the blocking result
Answer: stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue)
WhileSubscribed(5000) keeps the upstream active for five seconds after the last collector disappears, long enough to outlive a rotation but short enough to cancel work while the app is backgrounded.
How should one-off UI events such as 'navigate to detail' or 'show a snackbar' be handled relative to the screen's UI state?
- Stored permanently as boolean flags inside the immutable UI state class
- Emitted by the View directly, bypassing the ViewModel entirely
- Written to Logcat and re-read by the UI on every recomposition pass
- Modeled as consume-once state the UI marks handled so it won't re-fire
Answer: Modeled as consume-once state the UI marks handled so it won't re-fire
Current guidance models events as state that the UI consumes once and notifies the ViewModel about, which prevents a stale event from re-triggering after a recomposition or config change.
Where should business logic (e.g. the rules deciding whether an item can be bookmarked) and UI logic (e.g. formatting a timestamp for display) each live?
- Both belong inside the Composable functions that render the screen
- Business logic in the data/domain layer; UI logic in the UI layer
- Both belong exclusively in the ViewModel, never in other layers
- Business logic in the View; UI logic down in the repository
Answer: Business logic in the data/domain layer; UI logic in the UI layer
Business logic implements product requirements and lives in the data/domain layers, while UI logic decides how state is presented and belongs in the UI layer, keeping responsibilities cleanly separated.
A ViewModel survives configuration changes but not system-initiated process death. What is the recommended way to retain small pieces of UI state across process death?
- Store the values in a companion object
- Rely on viewModelScope to relaunch coroutines that rebuild the state
- Use SavedStateHandle, which is backed by saved instance state
- Persist every field to a remote server on each change
Answer: Use SavedStateHandle, which is backed by saved instance state
SavedStateHandle is backed by the saved instance state bundle, so small, critical pieces of state are restored when the system recreates the process, unlike plain ViewModel fields.
What is the safe, idiomatic way to update a single field of an immutable UI state held in a MutableStateFlow when updates may happen concurrently?
- Call update { it.copy(isLoading = true) } for an atomic change
- Mutate the field in place because data classes are mutable enough
- Read .value, change it, then assign .value again in a second step
- Swap in a new MutableStateFlow instance every time a field changes
Answer: Call update { it.copy(isLoading = true) } for an atomic change
MutableStateFlow.update {} performs a thread-safe compare-and-set, and copy() yields a new immutable instance with just the changed field, avoiding lost updates from a non-atomic read-modify-write.
Which property of StateFlow makes it a natural fit as a UI-state holder compared with a plain SharedFlow?
- It can hold several independent current values at the same time
- It always holds one current value, requires an initial, is conflated
- It buffers an unlimited history of every value it has ever emitted
- It starts empty and emits nothing until the first collector subscribes
Answer: It always holds one current value, requires an initial, is conflated
StateFlow always exposes a single current value, requires an initial value, is conflated, and skips emissions when the new value equals the previous one, which matches how a screen's state should behave.