TGViewer
Kotlin Kotlin @kotlin_lib · 2.05K subscribers
Post #698 1.02K
🐘 Ловушка MVI: Как мы превратили ViewModel в God-object

Все мы читали туториалы по MVI (Unidirectional Data Flow). Идея звучит безупречно: у вас есть один Intent (событие от юзера), один Reducer и один State (дата-класс, описывающий весь экран).

На экране авторизации это выглядит как поэзия. Но потом вы приходите на реальный прод.

Вам дают Главный Экран приложения: здесь лента постов, фильтры, баннеры, статус сети, профиль юзера в шапке и плеер в свернутом виде.

И тут начинается MVI-ад:


data class DashboardState(
val isLoading: Boolean = false,
val feed: List<Post> = emptyList(),
val searchQuery: String = "",
val activeFilters: Set<Filter> = emptySet(),
val userProfile: Profile? = null,
val unreadNotificationsCount: Int = 0,
val miniPlayerState: PlayerState = PlayerState.Idle,
// ... и еще 20 полей
)



В чем боль такого подхода?

1. Адские copy(): Пользователь вводит текст в строку поиска. На каждый символ вы делаете _state.value = _state.value.copy(searchQuery = newText). Вы пересоздаете гигантский объект состояния ради одного символа.

2. Конфликты Reducer'ов: Разные корутины грузят профиль, ленту и нотификации. Они начинают драться за актуальный state.value, затирая copy друг друга, если не использовать update { } (а с ним код становится еще более громоздким).

3. God-object ViewModel: Ваша ViewModel разрастается до 1500 строк, потому что она вынуждена имплементить интерфейсы плеера, пагинации, аналитики и поиска.

🛠 Как это чинят Сеньоры? Декомпозиция.

Прагматичный подход гласит: Не будьте догматиками. Один экран НЕ обязан иметь ровно один StateFlow.

Паттерн 1: Раздельные потоки (State Decomposition)
Вместо одного монолитного стейта, разбейте его на логические блоки внутри одной ViewModel:


class DashboardViewModel : ViewModel() {
val searchState: StateFlow<SearchState> = ...
val feedState: StateFlow<FeedState> = ...
val playerState: StateFlow<PlayerState> = ...
}



UI (Compose или Fragment) просто подписывается на нужные куски. Ввод текста в поиск больше не триггерит пересоздание списка постов.

Паттерн 2: Компонентный подход (Множественные ViewModel)
Кто сказал, что на Fragment/Activity может быть только одна ViewModel?
Если у вас на экране есть сложный независимый блок (например, Mini Player), дайте ему свою PlayerViewModel. Пусть она живет в скоупе Activity и отвечает только за плеер. Главный экран будет чище.

MVI это паттерн, а не религия. Умейте дробить стейт, когда он начинает пахнуть.

✍️ @kotlin_lib
  • 👍 7
More from @kotlin_lib
  1. Aug 26, 2026🚧 Ваш Mutex тормозит корутины. Как перестать лочить и начать жить Вы пишете многопоточный…
  2. Jul 25, 2026🚀 Подборка полезных IT каналов в Max Системное администрирование, DevOps 📌 https://max.r…
  3. Jul 1, 2026🌪 Ваша дата в опасности: flatMapConcat vs Merge vs Latest Вам нужно взять поток ID-шников…
  4. Jun 24, 2026🤖 Android-приложение — это не только красивый экран. За ним стоят работа с внешним API, з…
  5. Jun 23, 2026🔮 Убийца бойлерплейта: Встречайте Context Parameters (Kotlin 2.x) Мы так привыкли к Depen…
  6. Jun 3, 2026Яндекс обновил Yandex Mobile Ads SDK для монетизации мобильных приложений — и это интересн…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →