Как построить цепочку взаимодействия от ViewModel до запроса получения данных в Android
Что проверяют
Знаете ли вы слоистую архитектуру из официального руководства Google, кто за что отвечает и на каком потоке выполняется. Следом обычно просят показать, как это тестировать и где обрабатываются ошибки.
Как отвечать
Цепочка сверху вниз:
- UI: Compose-экран или фрагмент подписывается на состояние. В Compose это
collectAsStateWithLifecycle(), во фрагментеrepeatOnLifecycle(Lifecycle.State.STARTED), чтобы сбор останавливался в фоне; - ViewModel: держит
StateFlow<UiState>, гдеUiStateэто sealed-иерархия вроде Loading, Success, Error. Запускает загрузку вviewModelScope, наружу отдаёт неизменяемыйStateFlow, аMutableStateFlowдержит приватным; - UseCase: необязательный доменный слой, нужен, когда логику переиспользуют несколько экранов;
- Repository: единая точка доступа к данным. Часто это offline-first: данные из сети пишутся в Room, а экран читает
Flowиз DAO, база становится единственным источником правды; - Data sources: интерфейс Retrofit с
suspend-функциями и DAO Room. Оба сами уводят работу с главного потока, а блокирующий код репозиторий оборачивает вwithContext(Dispatchers.IO).
Зависимости приходят через конструктор, связывает всё Hilt: @HiltViewModel и @Inject constructor. Диспетчер тоже внедряется, и в тестах его подменяют на тестовый.
Холодный Flow превращают в состояние через stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initial), чтобы поворот экрана не перезапускал запрос. Ошибки сети ловят в репозитории или ViewModel и превращают в состояние Error, а не в падение.
Где спотыкаются
Создают репозиторий прямо в ViewModel через DataRepository(). Подменить его в тесте нельзя, а зависимость спрятана.
Передают во ViewModel Activity, View или контекст экрана. ViewModel живёт дольше активити, и это утечка памяти.
Ловят Exception целиком внутри корутины и глотают CancellationException. Отмена перестаёт работать, и корутина продолжает запрос после ухода с экрана.