Ответ
Стейт-машина в MVI (Model-View-Intent) — это концепция, где состояние экрана (State) изменяется предсказуемо в ответ на события (Intents) через чистые функции (редьюсеры). Каждое новое состояние — это неизменяемый объект, который View отображает.
Пример:
data class LoginState(
val isLoading: Boolean = false,
val error: String? = null,
val isSuccess: Boolean = false
)
fun reduce(oldState: LoginState, intent: LoginIntent): LoginState {
return when (intent) {
is LoginIntent.Submit -> oldState.copy(isLoading = true)
is LoginIntent.Error -> oldState.copy(isLoading = false, error = intent.message)
is LoginIntent.Success -> oldState.copy(isLoading = false, isSuccess = true)
}
}
Ключевые моменты:
- Состояние иммутабельно.
- Изменения только через редьюсеры.
- Предсказуемость: одинаковый интент + состояние = одинаковый результат.
Ответ 18+ 🔞
Э, слушай, давай разберём эту вашу стейт-машину в MVI, а то народ пугается, как будто это квантовая физика какая-то. На самом деле всё проще, чем кажется, если не забивать голову умными словами.
Представь себе, что у тебя есть экран — ну, например, логин. И у этого экрана есть состояние, оно же State. Это как настроение у человека: либо он спокойный, либо грузит мозги, либо обделался с ошибкой, либо, наоборот, такой довольный, что всё получилось. Всё это — разные состояния одного и того же чувака (экрана).
Вот мы это состояние описываем обычным дата-классом, чтоб всё по полочкам:
data class LoginState(
val isLoading: Boolean = false, // грузится ли он, как долбоёб
val error: String? = null, // если обосрался — тут будет сообщение об ошибке
val isSuccess: Boolean = false // если всё заебок — флаг в true
)
Теперь, интенты (Intents) — это как раз все события, которые могут с этим экраном произойти. Пользователь тыкнул кнопку «Отправить» — это интент. Сервер вернул ошибку — это интент. Всё прошло успешно — опять интент. Это просто сигналы: «эй, чувак, что-то случилось!».
А теперь самое важное, где вся магия и предсказуемость. У нас есть редьюсер — это такая чистая функция, которая берёт старое состояние и интент, который прилетел, и возвращает новое состояние. Без всяких побочных эффектов, без рандома, чистая математика, ёпта. Как в том анекдоте: «Дважды два — четыре, и хуй с горы».
fun reduce(oldState: LoginState, intent: LoginIntent): LoginState {
return when (intent) {
is LoginIntent.Submit -> oldState.copy(isLoading = true) // только начали грузиться
is LoginIntent.Error -> oldState.copy(isLoading = false, error = intent.message) // обосрались
is LoginIntent.Success -> oldState.copy(isLoading = false, isSuccess = true) // победили
}
}
Видишь? oldState.copy(...). Мы не меняем старое состояние, мы создаём новый объект на его основе. Это и есть иммутабельность. Старое состояние остаётся как было, его хоть в музей отправляй. А View просто берёт и отрисовывает то, что в новом состоянии прописано.
Итоговые ключевые моменты, чтобы в голове отложилось:
- Состояние иммутабельно. Это священная корова. Не трогай старое, создавай новое. Иначе потом сам от себя охуеешь, ища баг.
- Изменения только через редьюсеры. Это единственные ворота. Никаких прямых присваиваний полей из View или презентера. Всё через интенты и редьюсер. Порядок, блядь.
- Предсказуемость. Это главный плюс, ради которого всё и затевалось. Одинаковые входные данные (старое состояние + интент) всегда дают одинаковый результат. Никаких «ой, а тут у меня в прошлый раз сработало». Не, тут так не прокатит. Как дважды два.
Вот и вся философия. Не так страшен чёрт, как его малюют. Сначала кажется, что овердохуища кода для простой кнопки, но когда проект растёт и состояний становится много, эта предсказуемость начинает спасать жопу.