Combine two network sources using Coroutines and Flow

Tests your grasp of async data combination and state management. A good answer uses Flow.combine in a ViewModel, fed by a Repository, emitting a sealed class for UI state (Loading, Success, Error). A red flag is trying to combine state in the UI layer itself.
WHAT THIS TESTS: This is a core modern Android architecture question. It's not just about knowing the syntax of Flow.combine; it tests your ability to design a clean, resilient, and testable data flow from the network to the UI. The interviewer is looking for: 1) Correct use of structured concurrency and coroutine scopes (viewModelScope). 2) Efficient combination of asynchronous data streams without race conditions. 3) A robust state management pattern for the UI (loading, success, partial failure, total failure). 4) A clear separation of concerns between ViewModel, Repository, and DataSource.
A GOOD ANSWER COVERS: A senior-level answer should walk through the layers. First, define a sealed class for the UI state, like UiState<T> with Loading, Success(data: T), and Error(message: String) variants. Second, in the ViewModel, create a private _uiState as a MutableStateFlow and expose it as a public, immutable StateFlow. Third, within an init block or a function, launch a coroutine in viewModelScope. Inside, use repository.flowA.combine(repository.flowB) { resultA, resultB -> ... }. The combine block is where you merge the results into a single data model for the UI. Fourth, use .onStart { emit(UiState.Loading) } and .catch { emit(UiState.Error(...)) } on the combined flow to handle loading and error states before collecting into your _uiState. The Repository would expose two separate Flows, each coming from a different DataSource (e.g., a Retrofit service).
COMMON WRONG ANSWERS: A common mistake is to use two separate StateFlows in the ViewModel and try to combine them in the UI (Fragment/Activity). This couples the UI layer to business logic and is hard to manage. Another red flag is using nested launch blocks or async/await in a way that creates complex cancellation logic. Forgetting to use .catch() on the individual flows within the repository or on the combined flow is a major issue, as one failing network call would crash the entire coroutine. Suggesting zip instead of combine is also a nuanced mistake; zip waits for both sources to emit a new item together, while combine emits whenever either source emits a new value, which is usually the desired behavior for a reactive UI.
LIKELY FOLLOW-UPS: How would you handle a case where one request succeeds but the other fails? (Tests state modeling). How would you add caching to this flow? (Tests repository patterns and sources of truth). How would you write a unit test for this ViewModel logic? (Tests knowledge of runTest, Turbine, and mocking). What if one flow is fast and the other is very slow; how does combine behave?
ONE CONCRETE EXAMPLE: In the ViewModel, a concise implementation uses the stateIn operator: val uiState: StateFlow<UiState> = repository.getUserProfileFlow().combine(repository.getUserSettingsFlow()) { profile, settings -> UiState.Success(CombinedData(profile, settings)) }.onStart { emit(UiState.Loading) }.catch { emit(UiState.Error(it.message ?: "Unknown Error")) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UiState.Loading). This single expression demonstrates combine, state wrapping, error handling, and converting the cold Flow into a hot StateFlow shared among collectors.
Source: developer.android.com/topic/architecture
Read the original → developer.android.com
- #android
- #kotlin
- #coroutines
- #flow
- #architecture
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.