MVI — Model-View-Intent nümunəsinin mobil tətbiqlərdə mahiyyəti

Müəllif: IT Sectr Dərc olunub: 2026-02-16 Oxuma vaxtı: 10 dəq

MVI (Model-View-Intent) — birtərəfli verilən axınına və dəyişməz vəziyyətə əsaslanan reaktiv arxitektura nümunəsi. MVVM-dən fərqli olaraq, ViewModel bir çox StateFlow-a sahib ola bilər, MVI vahid vəziyyəti (State), dəyişməz niyyətləri (Intent) və təmiz reduksiya funksiyasını (Reducer) müəyyən edir. MVI istənilən zamanda ekran vəziyyətinin proqnozlaşdırıla bilməsini təmin edir. Nümunə Mosby və Orbit kitabxanaları tərəfindən Android icmasında populyarlaşdırılıb. Ətraflı — Arkadii Ivanov-dan MVIKotlin.

Əsas məqamlar

  • MVI — Model (vəziyyət), View (göstərmə), Intent (istifadəçi niyyəti) — reaktiv dövr
  • Unidirectional data flow — verilən bir istiqamətdə hərəkət edir: Intent → Reducer → State → View
  • Immutable State — ekranın vəziyyəti — hər dəyişiklikdə yenidən yaradılan dəyişməz obyekt
  • Reducer — cari vəziyyəti və Intent-i qəbul edən, yeni vəziyyət qaytaran təmiz funksiya
  • Side effects — yan təsirlər (şəbəkə, VB) Reducer-dən ayrı, Middleware vasitəsilə işlənir

MVI nədir: Model-View-Intent nümunəsinin mahiyyəti

MVI (Model-View-Intent) — Redux və Cycle.js prinsipləri üzərində qurulmuş reaktiv arxitektura nümunəsi. Model — ekranın dəyişməz vəziyyəti, Intent — istifadəçi və ya sistem niyyəti, View — vəziyyətə abunə olma və Intent göndərmə. Verilən dövr üzrə hərəkət edir: istifadəçi View ilə qarşılıqlı əlaqə yaradır → View Intent yaradır → Intent Reducer tərəfindən işlənir → Reducer yeni vəziyyət yaradır → View yeni vəziyyəti alır və yenidən çəkir.

MVI-nın MVVM-dən əsas fərqi — tək həqiqət mənbəyi (Single Source of Truth). MVVM-də ViewModel bir neçə LiveData/StateFlow-a (userState, loadingState, errorState) malik ola bilər, bu da uyğunsuzluğa səbəb olur: loading=true və user=null eyni anda. MVI-də ekranın bütün vəziyyətini təsvir edən tam bir sealed class/interface State mövcuddur. İstənilən zamanda ekran vəziyyəti birmənalı olaraq müəyyən edilmişdir — yüklənmiş verilən olduqda loading=true almaq mümkün deyil. IT Sectr-də biz MVI-nı mürəkkəb məntiqli ekranlar üçün tətbiq edirik — sifariş formaları, çoxaddımlı qeydiyyatlar, maliyyə ekranları — vəziyyətin proqnozlaşdırıla bilməsinin kritik olduğu yerlərdə.

KomponentMVI-də rolNümunə
Intentİstifadəçi və ya sistem niyyətiLoadUser, Refresh, SubmitForm
StateEkranın dəyişməz vəziyyətisealed class UserState
ReducerTəmiz funksiya: State + Intent → Statefun reduce(state, intent) -> state
MiddlewareYan təsirlərin işlənməsiŞəbəkə sorğusu, VB-yə yazı

MVI dövrü beş addımdan ibarətdir: 1) View Intent göndərir (məsələn, LoadUser(42)); 2) Middleware (EffectHandler) yan təsiri yerinə yetirir — şəbəkə sorğusu; 3) Nəticə sistemə yeni Intent kimi qayıdır; 4) Reducer cari vəziyyəti və Intent-i qəbul edir, yeni vəziyyət yaradır; 5) View yeni vəziyyəti alır və yenidən çəkir. Hər addım proqnozlaşdırıla biləndir və təcrid olunmuş şəkildə test edilir.

Android-də MVI: Kotlin-də Intent, Reducer, State

Android-də MVI Intent və State üçün sealed-klaslar, MVI-məntiqli ViewModel və reaktiv göstərmə üçün Jetpack Compose vasitəsilə tətbiq edilir. ViewModel View-dan Intent qəbul edir, yan təsirləri Middleware-ə həvalə edir, Reducer-i işə salır və StateFlow vasitəsilə yeni vəziyyəti dərc edir. Jetpack Compose state dəyişdikdə UI-ni yenidən çəkir — MVI dövrü üçün idealdır.

kotlin
// Intent — istifadəçi niyyətləri
sealed interface UserIntent {
    data class LoadUser(val userId: Int) : UserIntent
    data object Refresh : UserIntent
}

// State — ekranın vahid vəziyyəti
sealed interface UserState {
    data object Idle : UserState
    data object Loading : UserState
    data class Success(val user: User) : UserState
    data class Error(val message: String) : UserState
}

// Reducer — təmiz funksiya
object UserReducer {
    fun reduce(state: UserState, intent: UserIntent): UserState = when (intent) {
        is UserIntent.LoadUser -> UserState.Loading
        is UserIntent.Refresh -> UserState.Loading
    }
}

// MVI ilə ViewModel
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {

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

    fun process(intent: UserIntent) {
        val newState = UserReducer.reduce(_state.value, intent)
        _state.value = newState
        when (intent) {
            is UserIntent.LoadUser -> loadUser(intent.userId)
            is UserIntent.Refresh -> loadUser(/* əvvəlki ID */)
        }
    }

    private fun loadUser(userId: Int) {
        viewModelScope.launch {
            repository.getUser(userId)
                .onSuccess { user ->
                    _state.value = UserState.Success(user)
                }
                .onFailure { e ->
                    _state.value = UserState.Error(e.message ?: "Error")
                }
        }
    }
}

// View Intent göndərir
fun UserScreen(viewModel: UserViewModel) {
    val state by viewModel.state.collectAsState()
    LaunchedEffect(Unit) { viewModel.process(UserIntent.LoadUser(42)) }
    when (state) {
        is UserState.Loading -> CircularProgressIndicator()
        is UserState.Success -> UserCard((state as UserState.Success).user)
        is UserState.Error -> ErrorView((state as UserState.Error).message)
        is UserState.Idle -> Text("Press to load")
    }
}

Middleware və yan təsirlər — MVI-də təmiz Reducer şəbəkə sorğularını yerinə yetirə bilməz. Middleware (həmçinin EffectHandler və ya Bootstrapper) Intent-i emal edir, yan təsiri yerinə yetirir və dövrəyə yeni Intent göndərir. Orbit MVI və MVIKotlin kitabxanaları test edilə bilən effektlərlə daxili Middleware dəstəyi təqdim edir. Middleware olmadan MVI əlavə Intent və State strukturu ilə MVVM-ə çevrilir.

Arkadii Ivanov-dan MVIKotlin — Kotlin Multiplatform üçün ən populyar MVI kitabxanası. Android, iOS, web və JVM-i dəstəkləyir. Komponentlər təqdim edir: Store (ViewModel), Bootstrapper (ilkin effektlər), Reducer, Middleware. 2025-ci ilin oktyabrına kitabxana GitHub-da 2,5K ulduz toplayıb və kommersiya layihələrində, o cümlədən böyük Rusiya banklarının tətbiqlərində istifadə olunur. IT Sectr-də biz ümumi biznes məntiqi ilə KMP çarpaz-platforma layihələri üçün MVIKotlin-dən istifadə edirik.

iOS-də MVI: Swift-də birtərəfli axın

iOS-də MVI Combine-ViewModel olmadan, Intent → State dövrü vasitəsilə tətbiq edilir. View bağlanma (closure) vasitəsilə Intent göndərir, Reducer — təmiz funksiya, State — dəyişməz sahələri olan struct. SwiftUI State dəyişdikdə View-i yenidən çəkir, bu da əlavə @Published xüsusiyyətləri olmadan MVI dövrünə mükəmməl uyğun gəlir. iOS-də MVI xüsusilə Redux-dan (JavaScript) keçmiş SwiftUI tərtibatçıları icmasında populyardır.

swift
// State — dəyişməz struktur
struct UserState: Equatable {
    var user: User?
    var isLoading = false
    var errorMessage: String?
}

// Intent — niyyətləri olan enum
enum UserIntent {
    case loadUser(id: Int)
    case refresh
    case userLoaded(User)
    case loadFailed(Error)
}

// Reducer — təmiz funksiya
func userReducer(state: UserState, intent: UserIntent) -> UserState {
    var newState = state
    switch intent {
    case .loadUser, .refresh:
        newState.isLoading = true
        newState.errorMessage = nil
    case .userLoaded(let user):
        newState.isLoading = false
        newState.user = user
    case .loadFailed(let error):
        newState.isLoading = false
        newState.errorMessage = error.localizedDescription
    }
    return newState
}

// Store — vəziyyətə sahibdir və effektləri idarə edir
final class UserStore: ObservableObject {
    @Published private(set) var state = UserState()
    private let service: UserService

    init(service: UserService) {
        self.service = service
    }

    func dispatch(_ intent: UserIntent) {
        // 1. Reducer vəziyyəti yeniləyir
        state = userReducer(state: state, intent: intent)
        // 2. Side effects (lazım olduqda)
        switch intent {
        case .loadUser(let id), .refresh:
            service.fetchUser(id: id) { [weak self] result in
                switch result {
                case .success(let user):
                    self?.dispatch(.userLoaded(user))
                case .failure(let error):
                    self?.dispatch(.loadFailed(error))
                }
            }
        default: break
        }
    }
}

TCA (The Composable Architecture) — Point-Free-dən iOS üçün ən populyar MVI tətbiqi, SwiftUI və Combine üzərində qurulub. TCA Store, Reducer, Effect və Environment təqdim edir. 2025-ci ilin oktyabrına GitHub ulduz sayı 13K-nı keçib — bu iOS üçün MVI-nın de facto standartıdır. TCA Starbucks, Airbnb (qismən) və bir çox indie layihələrinin tətbiqlərində istifadə olunur. Özəl yazılmış MVI-dən fərqli olaraq, TCA test etmə, naviqasiya və yan təsir məsələlərini qutudançıxma həll edir.

iOS-də MVI vs MVVM — TCA/MVI vəziyyətin proqnozlaşdırıla bilməsini təmin edir, lakin daha çox şablon kodu tələb edir (Reducer, State, Action). @Published ilə MVVM sadə ekranlar üçün daha asandır. IT Sectr-də biz ekranların 80%-də MVVM, 20% mürəkkəb ekranlarda isə MVI (TCA) istifadə edirik — maliyyə əməliyyatları, çoxaddımlı formalar, drag-and-drop interfeysləri, vəziyyət səhvinin istifadəçiyə pula başa gələ biləcəyi yerlər.

MVI-nın MVVM ilə müqayisəsi: nə vaxt MVI seçməli

MVI və MVVM eyni vəzifəni həll edir — Presentation qatının təşkili — lakin vəziyyətin idarə edilməsinə fərqli yanaşmalarla. MVVM çoxsaylı reaktiv mənbələrə (LiveData, @Published) icazə verir, bu da uyğunsuzluğa səbəb ola bilər. MVI hər an tam bir vəziyyətə zəmanət verir, bu da onu daha ciddi və proqnozlaşdırıla bilən edir, lakin kodun həcmini artırır.

MeyarMVVMMVI
VəziyyətÇoxsaylı LiveData/StateFlowVahid sealed class State
Verilən axınıİkitərəfli (View → ViewModel, LiveData → View)Birtərəfli (Intent → Reducer → State → View)
Yan təsirlərBirbaşa ViewModel-dəMiddleware/EffectHandler vasitəsilə
Test etməViewModel-in vahid testləriReducer + Middleware-in vahid testləri
Şablon koduMinimalReducer + State + Intent + Middleware

Nə vaxt MVI seçməli — vəziyyətin ciddi deterministik olmalı olduğu ekranlar: maliyyə əməliyyatları, onlayn mağaza səbəti, hər addımda validasiya ilə çoxaddımlı formalar. Bu ssenarilərdə vəziyyət səhvinin dəyəri (məsələn, iki LiveData yarışı səbəbindən səbət məbləğini bir məhsul olmadan göstərmək) əlavə kodun dəyərindən yüksəkdir. MVVM-də siz komandanın intizamına, MVI-də isə arxitekturaya güvənirsiniz.

Nə vaxt MVVM kifayətdir — 80% standart ekranlar: istifadəçi siyahısı, profil, parametrlər, xəbər lenti. Burada tək vəziyyət həddindən artıqdır, MVI-nın əlavə strukturu isə inkişafı yavaşladacaq. IT Sectr-də qayda belədir: ekranın keçidlərlə 3+ mümkün vəziyyəti varsa (yükləmə → verilən → xəta → təkrar → yükləmə → verilən) — MVI. Ekranda 1-2 asinxron əməliyyat varsa — MVVM.

MVI-nın ən yaxşı təcrübələri və tipik səhvlər

Sealed State — MVI-nın ən yaxşı təcrübəsi. Vəziyyət Loading, Success(data), Error(message) variantları ilə sealed class/interface kimi müəyyən edilir. Bu, View-in uyğunsuz vəziyyətə düşməməsinə zəmanət verir — loading=true olduqda verilən göstərilə bilməz, çünki Loading və Success fərqli siniflərdir. Vəziyyətə aid bütün verilən sealed variantının daxilindədir: Success istifadəçini, Error isə xəta mesajını ehtiva edir.

Reducer təmiz funksiya olaraq qalmalıdır — API, VB, SharedPreferences çağırışları olmadan. Təmiz funksiya State və Intent qəbul edir, State qaytarır. Yan təsirlər (şəbəkə, VB, naviqasiya, tostlar) Middleware-də və ya Reducer çağırışından sonra Store.dispatch-də işlənir. Əgər Reducer yan təsirlərlə çirklənibsə, MVI test edilə bilmə və proqnozlaşdırıla bilmə xüsusiyyətlərini itirir — üstünlükləri olmadan əlavə strukturlu MVVM əldə edirsiniz.

Tipik səhvlər — State-i sealed class əvəzinə nullable sahələri olan data class kimi elan etmək: data class UserState(val user: User?, val isLoading: Boolean, val error: String?). Bu MVVM-in ekvivalentidir, MVI deyil — View sahə kombinasiyalarını etibarlılıq üçün yoxlamalıdır. Sealed yanaşmasında etibarsız kombinasiyalar (isLoading=true və user!=null) tip səviyyəsində qeyri-mümkündür. İkinci səhv — biznes məntiqini Intent-də yerləşdirmək (Intent.LoadUserBeforeXHours) əvəzinə sadə komanda Intent-ləri (Intent.LoadUser) yaratmaq, biznes məntiqini isə Middleware-də yerləşdirmək.

Tez-tez verilən suallar

MVI-nın MVVM-dən əsas fərqi nədir?

MVI vahid dəyişməz sealed State sinfi və Reducer vasitəsilə birtərəfli verilən axınından istifadə edir. MVVM ikitərəfli əlaqə ilə çoxsaylı LiveData/StateFlow-a icazə verir. MVI tip səviyyəsində vəziyyətin uyğunluğuna zəmanət verir — loading=true və user=null eyni anda almaq mümkün deyil. MVVM tərtibatçının intizamına güvənir.

Android üçün hansı MVI kitabxanaları mövcuddur?

Əsas: MVIKotlin (Arkadii Ivanov, 2,5K ulduz, Kotlin Multiplatform), Orbit MVI (BabyJ, 1,3K ulduz), Mobius (Spotify, Kotlin/Java). MVIKotlin Kotlin üçün ən populyar, Orbit öyrənmək üçün ən sadədir. Hər üçü test edilə bilən Reducer və Middleware dəstəkləyir. Jetpack Compose üçün sealed State + Reducer vasitəsilə kitabxanasız sadə MVI yazmaq kifayətdir.

MVI üçün ayrıca kitabxana lazımdırmı?

Xeyr — sealed Intent + sealed State + ViewModel + StateFlow asılılıqlar olmadan işləyən MVI verir. Kitabxanalar (MVIKotlin, Orbit, TCA) Middleware, yan təsirlərin test edilməsi və DI ilə inteqrasiya əlavə edir. Sadə layihələr üçün kitabxananın çəkisi əsassızdır. 20+ ekranlı mürəkkəb layihələr üçün kitabxana effektlərin strukturlaşdırılmış işlənməsi ilə özünü doğruldur.

MVI iOS üçün uyğundurmu yoxsa bu Android nümunəsidir?

MVI iOS üçün TCA (The Composable Architecture) vasitəsilə mükəmməl uyğundur — SwiftUI icmasının ən populyar arxitekturası. TCA əslində MVI + Redux + Combine-dir. iOS-da MVI-nı TCA olmadan da ObservableObject və təmiz reducer funksiyası vasitəsilə tətbiq etmək olar. SwiftUI dəyişməz State ilə MVI dövrünə mükəmməl uyğun gəlir.

MVI-nı necə test etməli?

Reducer təmiz funksiya kimi vahid testlərlə test edilir: ilkin State verilir, Intent göndərilir, yekun State yoxlanılır. Middleware mock-repozitori ilə test edilir: LoadUser-dan sonra getUser-ın çağırıldığı yoxlanılır. ViewModel-test: Intent göndərmək, StateFlow-u yoxlamaq. MVI MVVM-dən daha asan test edilir, çünki Reducer gizli asılılıqları olmayan təmiz funksiyadır.

Nəticə

  • MVI (Model-View-Intent) — birtərəfli axın və tək vəziyyət ilə reaktiv nümunə
  • Sealed State — tip səviyyəsində uyğunluğa zəmanət verir, etibarsız kombinasiyaları istisna edir
  • Reducer — State + Intent → State təmiz funksiyası, mock-obyektlər olmadan test edilir
  • Middleware — yan təsirlər (şəbəkə, VB, naviqasiya) üçün ayrıca təbəqə
  • MVI vs MVVM — MVI daha ciddi və proqnozlaşdırıla biləndir, MVVM daha sadə və sürətlidir
  • Android — mürəkkəb ekranlar üçün MVIKotlin və ya Orbit; sadə ekranlar üçün MVVM
  • iOS — TCA (The Composable Architecture) — SwiftUI-də MVI standartı

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun