Настройка архитектуры MVI для Android-приложения

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Настройка архитектуры MVI для Android-приложения
Сложный
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Настройка архитектуры MVI для Android-приложения

MVI — Model-View-Intent — это не просто эволюция MVVM. Это смена парадигмы: вместо двусторонних привязок данных вы получаете однонаправленный поток, где состояние UI предсказуемо в любой момент времени. Это особенно важно, когда пользователь одновременно тянет список вниз для обновления, нажимает кнопку и приходит push-уведомление — три события, которые MVVM с мутабельными LiveData может обработать в непредсказуемом порядке.

Принципы MVI, которые меняют подход к отладке

Единственный источник истины — UiState. Весь экран описывается одной иммутабельной структурой. Нет isLoading = true в одном месте и showError() в другом — есть UiState.Loading, UiState.Success(data), UiState.Error(message). Текущее состояние экрана — всегда один объект.

Intent — не Android Intent. В MVI это действие пользователя: RefreshIntent, SearchIntent(query), LoadMoreIntent. ViewModel принимает поток Intent-ов и преобразует их в состояния.

Воспроизводимость. Если знаешь начальное состояние и последовательность Intent-ов — можешь точно воспроизвести итоговое состояние. Это делает баг-репорты тестируемыми.

Реализация на Kotlin + Coroutines

data class ProfileUiState(
    val isLoading: Boolean = false,
    val profile: UserProfile? = null,
    val error: String? = null,
    val isRefreshing: Boolean = false
)

sealed class ProfileIntent {
    data class Load(val userId: String) : ProfileIntent()
    object Refresh : ProfileIntent()
    data class Follow(val targetId: String) : ProfileIntent()
}

@HiltViewModel
class ProfileViewModel @Inject constructor(
    private val getProfile: GetUserProfileUseCase,
    private val followUser: FollowUserUseCase
) : ViewModel() {

    private val _state = MutableStateFlow(ProfileUiState())
    val state: StateFlow<ProfileUiState> = _state.asStateFlow()

    fun processIntent(intent: ProfileIntent) {
        when (intent) {
            is ProfileIntent.Load -> loadProfile(intent.userId)
            is ProfileIntent.Refresh -> refreshProfile()
            is ProfileIntent.Follow -> followUser(intent.targetId)
        }
    }

    private fun loadProfile(userId: String) {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true, error = null) }
            getProfile(userId).fold(
                onSuccess = { _state.update { s -> s.copy(isLoading = false, profile = it) } },
                onFailure = { _state.update { s -> s.copy(isLoading = false, error = it.message) } }
            )
        }
    }
}

В Composable:

val state by viewModel.state.collectAsStateWithLifecycle()

LaunchedEffect(userId) {
    viewModel.processIntent(ProfileIntent.Load(userId))
}

Кнопка отправляет viewModel.processIntent(ProfileIntent.Follow(targetId)) — и никакой прямой мутации UI.

Side Effects: канал для одноразовых событий

StateFlow не подходит для навигации и показа Toast: они не «состояния», а «события». Для них используем Channel или SharedFlow:

private val _effects = Channel<ProfileEffect>(Channel.BUFFERED)
val effects = _effects.receiveAsFlow()

sealed class ProfileEffect {
    data class NavigateToEdit(val userId: String) : ProfileEffect()
    data class ShowSnackbar(val message: String) : ProfileEffect()
}

В Fragment/Activity подписываемся на effects в lifecycleScope.launch { viewModel.effects.collect { ... } }.

Сравнение с MVVM в контексте сложных экранов

Характеристика MVVM MVI
Состояние Несколько StateFlow Один UiState
Предсказуемость Зависит от дисциплины Архитектурно гарантирована
Параллельные события Возможны race conditions Обрабатываются последовательно
Тестируемость Хорошая Отличная (Given/When/Then по состояниям)
Порог входа Низкий Средний

Для простых CRUD-экранов MVVM достаточен. MVI оправдан при: экранах с несколькими источниками событий, сложных UI-состояниях с несколькими флагами, командах с высокими требованиями к тестовому покрытию.

Orbit MVI — готовый фреймворк

Писать MVI с нуля на каждом проекте — дублирование. Orbit MVI (orbit-mvi) — библиотека от Mobile Native Foundation, которая даёт лаконичный DSL:

class ProfileViewModel : ContainerHost<ProfileUiState, ProfileEffect>, ViewModel() {
    override val container = container<ProfileUiState, ProfileEffect>(ProfileUiState())

    fun load(userId: String) = intent {
        reduce { state.copy(isLoading = true) }
        val profile = getProfile(userId).getOrThrow()
        reduce { state.copy(isLoading = false, profile = profile) }
    }
}

orbit-mvi совместим с Hilt и хорошо тестируется через test { } блок из orbit-testing.

Что входит в настройку

Выбор подхода: ручная реализация или Orbit MVI. Настройка базового контракта UiState/Intent/Effect. Реализация образцового модуля с тестами через turbine + kotlinx-coroutines-test. Документация для команды с примерами обработки edge cases.

Сроки

Настройка MVI с нуля (структура + первый модуль с тестами): 3–5 дней. Миграция MVVM-проекта на MVI: 2–4 недели. Стоимость — после анализа.