Site icon AppTractor

Архитектура Android переходит от Event-Driven UI к State-Driven UI

Официальные рекомендации Google по Android были обновлены 18 мая 2026 года — и теперь они прямо рекомендуют превращать события ViewModel в состояние UI.

Долгое время архитектура пользовательского интерфейса в Android часто выглядела примерно так:

ViewModel
   │
   ├── UiState ───────→ UI
   │
   └── UiEvent/Effect → UI
          │
          ├── Channel
          ├── SharedFlow
          └── Event wrapper

Состояние — это то, за чем UI наблюдает постоянно. События или эффекты — это то, что UI должен обработать только один раз. Такой подход давно распространён в архитектурах MVVM и MVI. Однако если посмотреть на актуальные рекомендации Android-разработки по событиям UI, Google предлагает пересмотреть саму эту модель.

Теперь Google прямо говорит:

События ViewModel всегда должны приводить к обновлению состояния UI, — UI events | Android Developers

Это означает, что по крайней мере для взаимодействия ViewModel → UI идея:

Нам нужен Event или Effect, потому что одного State недостаточно

больше не является рекомендуемым подходом.

Подход, основанный на событиях

Традиционная реализация может выглядеть так:

sealed interface UiEvent {
    data class ShowSnackbar(
        val message: String
    ) : UiEvent
}


private val _events = Channel<UiEvent>()
val events = _events.receiveAsFlow()

fun save() {
    viewModelScope.launch {
        repository.save()

        _events.send(
            UiEvent.ShowSnackbar("Saved successfully")
        )
    }
}

UI получает событие:

LaunchedEffect(Unit) {
    viewModel.events.collect { event ->
        when (event) {
            is UiEvent.ShowSnackbar ->
                snackbarHostState.showSnackbar(event.message)
        }
    }
}

Фактически ViewModel говорит UI: «Покажи Snackbar». Это подход, основанный на событиях.

Подход, основанный на состоянии

Текущие рекомендации Android предлагают двигаться в другом направлении.

Вместо отправки ShowSnackbar ViewModel обновляет состояние UI:

data class UiState(
    val userMessage: String? = null
)


private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
        
fun save() {
    viewModelScope.launch {
        repository.save()

        _uiState.update {
            it.copy(userMessage = "Saved successfully")
        }
    }
}

fun userMessageShown() {
    _uiState.update {
        it.copy(userMessage = null)
    }
}

UI наблюдает за состоянием:

LaunchedEffect(uiState.userMessage) {
    uiState.userMessage?.let { message ->
        snackbarHostState.showSnackbar(message)
        viewModel.userMessageShown()
    }
}

Теперь ViewModel не говорит: «Покажи Snackbar». Она сообщает: «Сейчас есть сообщение, которое нужно показать». А UI уже сам решает, как именно его отобразить.

Важное различие

Разница не просто в замене:

Channel → StateFlow

Разница в модели взаимодействия:

Event-Driven:

ViewModel
 ↓
"Do this"
 ↓
UI

State-Driven:

ViewModel
 ↓
"This is the current state"
 ↓
UI

Google также формулирует это различие так:

Состояние существует. События происходят.

События никуда не исчезают. Нажатие кнопки по-прежнему является событием:

User
 ↓
Button.onClick
 ↓
ViewModel
 ↓
State
 ↓
UI

Главное изменение заключается в том, что взаимодействие ViewModel → UI всё больше строится вокруг состояния, а не одноразовых событий.

Значит ли это, что события больше не нужно использовать?

Нет. В официальных рекомендациях не говорится, что Channel, SharedFlow или события запрещены. Вместо этого Google предлагает каждый раз задаваться вопросом: можно ли событие из ViewModel представить в виде состояния UI?

Полезный вопрос при проектировании ViewModel звучит так:

Должна ли ViewModel говорить UI, что именно делать, или она может просто описать, что сейчас является истинным?

Такое небольшое изменение в мышлении может фундаментально изменить подход к проектированию архитектуры пользовательского интерфейса в Android.

Источник

Exit mobile version