Как устроена архитектура MVVM с Update, CommandHandler и UIStateMapper
Что проверяют
Понимаете ли вы однонаправленный поток данных: откуда приходит событие, где меняется состояние, где живут побочные эффекты и кто готовит данные для экрана. Такие названия обычно из внутренней архитектуры конкретной команды, по сути это The Elm Architecture внутри ViewModel. Следом часто просят показать, как такой экран тестируется.
Как отвечать
Оговоритесь, что названия в командах разные, и опишите роли в замкнутом цикле:
- Event (Message): всё, что может изменить экран. Действия пользователя (
RetryClicked) и результаты работы (DataLoaded,LoadFailed); - State: состояние экрана в терминах предметной области, неизменяемый
data class; - Update: чистая функция
(State, Event) -> Pair<State, Set<Command>>. Внутри нет корутин, сети и Android, только решение: каким стало состояние и какие эффекты запустить; - Command: описание побочного эффекта как данных, например
LoadProfile(id); - CommandHandler: исполняет команды. Ходит в репозиторий или use case в
viewModelScope, результат превращает обратно в Event и отдаёт в Update; - UIStateMapper: чистая функция
State -> UiState. Форматирует даты и суммы, собирает строки, решает, показывать ли загрузку, аStateостаётся свободным от деталей отображения.
ViewModel хранит MutableStateFlow<State>, наружу отдаёт StateFlow<UiState> через map и stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initial). Compose читает его через collectAsStateWithLifecycle(). Одноразовые эффекты вроде навигации идут через Channel или отдельным полем состояния.
Плюс схемы: Update и маппер тестируются обычными юнит-тестами без моков и корутин, а CommandHandler проверяется с фейковым репозиторием.
Где спотыкаются
Переворачивают роли по привычке к классическому MVVM: будто CommandHandler принимает клики от View. На деле команды выдаёт Update, а обработчик только исполняет эффекты.
Делают сетевой запрос прямо в Update. Функция перестаёт быть чистой, и пропадает весь смысл схемы с предсказуемыми тестами.
Кладут в State готовые строки и цвета. Тогда маппер не нужен, зато State зависит от ресурсов и локали, и тестировать его становится сложнее.