Налаштування архітектури 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: вони не «стани», а «evento». Для них використовуємо 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 тижні. Вартість — після аналізу.