MVVM (Model-View-ViewModel) — ViewModel-in Presenter-i əvəz etdiyi və View ilə əlaqə üçün reaktiv mexanizmlərdən istifadə edən memarlıq patternidir: SwiftUI-də ObservableObject, Android-də LiveData/StateFlow. ViewModel-in View-ə istinadı yoxdur — məlumatlar abunə vasitəsilə ötürülür, bu da ViewContract interfeyslərinə ehtiyacı aradan qaldırır və testləri daha da asanlaşdırır. Apple 2019-cu ildən SwiftUI ilə MVVM-i, Google isə Jetpack ilə MVVM-i Android-in rəsmi memarlığı kimi tövsiyə edir. Ətraflı Android Architecture Guide-də.
Əsas
MVVM (Model-View-ViewModel) — 2005-ci ildə John Gossman tərəfindən Microsoft-un Windows Presentation Foundation (WPF) üçün təsvir edilmiş memarlıq patternidir. ViewModel — ekranın vəziyyətini və biznes məntiqini ehtiva edən, lakin View-ə istinadı olmayan mərkəzi komponentdir. Məlumatlar reaktiv bağlama mexanizmləri vasitəsilə ötürülür: View ViewModel-in dəyişikliklərinə abunə olur və məlumatlar dəyişdikdə avtomatik yenidən çəkilir.
MVVM-nin MVP-dən əsas fərqi — ViewContract-in olmaması. MVP-də Presenter view.showUser(data) metodlarını çağırır, yəni Presenter aktiv şəkildə məlumatları View-ə “ötürür”. MVVM-də View özü məlumatları ViewModel-dən abunə vasitəsilə “cəkir”: ViewModel abunəçisinin olub-olmadığını bilmir. Bu, ayrılmış View problemini aradan qaldırır — əgər Activity fırlanma zamanı məhv olarsa, ViewModel işə davam edir, yeni Activity isə sadəcə aktual məlumatlara abunə olur. IT Sectr-də 2020-ci ildən bütün yeni layihələrdə MVVM istifadə edirik — kod daha proqnozlaşdırıla bilən, testlər daha sabit oldu.
| Komponent | Məsuliyyət | Platforma |
|---|---|---|
| Model | Məlumatlar, biznes məntiqi, repozitorilər | Android/iOS |
| View | Göstərmə, ViewModel-ə abunə | Activity/Composable, UIView/SwiftUI View |
| ViewModel | Ekranın vəziyyəti, məntiq, naviqasiya | ViewModel (Jetpack), ObservableObject |
Reaktiv bağlama — MVVM-in təməli. Android-də LiveData (Jetpack-in hissəsi) müşahidə olunan məlumat anbarıdır. Activity observe() vasitəsilə abunə olur: viewModel.user.observe(this) { user -> binding.name.text = user.name }. user dəyişdikdə bütün abunəçilər avtomatik olaraq yeni dəyər alır. iOS-da SwiftUI ViewModel-də @Published xassələrindən istifadə edir — dəyişikliklər avtomatik olaraq View-i yenidən çəkir. Bu, MVP-də tələb olunan əl ilə showUser/hideLoading çağırışlarını aradan qaldırır.
Jetpack-dən ViewModel — Google-dan MVVM-in tətbiqi üçün rəsmi komponent. ViewModel ekranın fırlanmasından sağ çıxır: konfiqurasiya dəyişdikdə Activity məhv olur və yenidən yaradılır, ViewModel isə yaddaşda qalır. Activity-nin yeni nümunəsi ViewModelProvider vasitəsilə eyni ViewModel-i alır. ViewModel-in Activity-yə, Context-ə və ya View-ə istinadları yoxdur — o, təmizdir və Robolectric olmadan unit-testlərlə test edilir.
// ViewModel StateFlow ilə — MVVM-in müasir tətbiqi
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _state = MutableStateFlow<UserState>(UserState.Loading)
val state: StateFlow<UserState> = _state.asStateFlow()
fun loadUser(userId: Int) {
viewModelScope.launch {
_state.value = UserState.Loading
repository.getUser(userId)
.onSuccess { user ->
_state.value = UserState.Success(user)
}
.onFailure { e ->
_state.value = UserState.Error(e.message ?: "Unknown")
}
}
}
}
sealed interface UserState {
data object Loading : UserState
data class Success(val user: User) : UserState
data class Error(val message: String) : UserState
}
// View (Activity) state-ə abunə olur
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.state.onEach { state ->
when (state) {
is UserState.Loading -> /* yükləməni göstər */
is UserState.Success -> /* məlumatları göstər */
is UserState.Error -> /* xətanı göstər */
}
}.launchIn(lifecycleScope)
viewModel.loadUser(42)
}
}
LiveData vs StateFlow — LiveData (2017) — Activity lifecycle-i üçün optimallaşdırılmış ilk Jetpack reaktiv komponenti: onStop-da avtomatik abunədən çıxma. StateFlow (2021) — lifecycle-ə bağlı olmayan, lakin lifecycleScope vasitəsilə əl ilə abunədən çıxma tələb edən Kotlin Flow tətbiqi. StateFlow coroutines, concat, map və LiveData-da olmayan digər Flow operatorlarını dəstəkləyir. IT Sectr-də bütün yeni ViewModel-lər üçün StateFlow istifadə edirik — o, daha qısa, daha güclüdür və korutinlərlə daha yaxşı inteqrasiya olunur.
DataBinding və ViewBinding — DataBinding ViewModel-i XML ilə @{viewModel.user.name} vasitəsilə birbaşa layoutda birləşdirir, Activity-də kodu aradan qaldırır. ViewBinding View-ə giriş üçün tip təhlükəsiz sinif yaradır. Google sadə layihələr üçün ViewBinding, mürəkkəb məlumat bağlaması olan layihələr üçün DataBinding tövsiyə edir. Jetpack Compose-da DataBinding lazım deyil — @Composable funksiyaları State dəyişdikdə avtomatik yenidən çəkilir.
iOS-da MVVM Combine-dən ObservableObject vasitəsilə tətbiq olunur. ViewModel — ObservableObject-dən miras alan sinifdir, @Published sahələri ilə. SwiftUI View @ObservedObject və ya @StateObject vasitəsilə ViewModel-ə abunə olur. @Published xassəsi dəyişdikdə SwiftUI bu xassədən asılı olan View-i avtomatik yenidən çəkir. Apple SwiftUI-ni 2019-cu ildə WWDC-də Combine ilə birlikdə təqdim etdi — bu andan MVVM iOS üçün rəsmi olaraq tövsiyə olunan pattern oldu.
import SwiftUI
import Combine
// ViewModel — @Published sahələri olan ObservableObject
final class UserViewModel: ObservableObject {
@Published private(set) var state: UserState = .loading
private let service: UserService
init(service: UserService) {
self.service = service
}
func loadUser(id: Int) {
state = .loading
service.fetchUser(id: id) { [weak self] result in
guard let self else { return }
switch result {
case .success(let user):
self.state = .success(user)
case .failure(let error):
self.state = .error(error.localizedDescription)
}
}
}
}
enum UserState {
case loading
case success(User)
case error(String)
}
// SwiftUI View — ViewModel-ə abunə olur
struct UserView: View {
@StateObject private var viewModel: UserViewModel
var body: some View {
switch viewModel.state {
case .loading:
ProgressView()
case .success(let user):
VStack {
Text(user.name).font(.title)
Text(user.email).font(.body)
}
case .error(let message):
Text(message).foregroundColor(.red)
}
}
}
@StateObject vs @ObservedObject — @StateObject ViewModel yaradır və onun həyat dövrünü idarə edir (View-in ömrü boyu bir dəfə). @ObservedObject — ViewModel xaricdə yaradılır və View-ə ötürülür. WWDC 2022 yaratmaq üçün @StateObject və ViewModel-i Views arasında ötürmək üçün @ObservedObject tövsiyə edir. iOS 17-də (2023) @Observable peyda oldu — abunəni avtomatlaşdıran və @Published annotasiyalarını aradan qaldıran makro. @Observable — Combine-in təkamülüdür, iOS inkişafını Kotlin Flow reaktivliyinə yaxınlaşdırır.
UIKit + MVVM — UIKit-də (SwiftUI olmadan) layihələr üçün MVVM Combine və @Published vasitəsilə UIViewController-də sink() ilə abunə olaraq tətbiq olunur. ViewModel eynidir, View — @Published-ə abunə olan UIViewController. Combine iOS 13-dən (2019) etibarən mövcuddur və sistemə daxildir — əlavə asılılıq tələb etmir. Apple Developer Survey (2025) məlumatına görə, iOS layihələrinin 45%-i hətta UIKit-də Combine istifadə edir, 35%-i SwiftUI + Combine istifadə edir, 20%-i — RxSwift (legacy).
MVVM MVP-dən üstündür üç əsas aspektdə: ViewContract interfeyslərinin olmaması, abunələrin avtomatik idarə edilməsi və ekranın fırlanmasından sağ çıxma. MVP-də hər ekran ViewContract interfeysi + Presenter sinfi + onStart/onStop-da abunə/abunədən çıxma tələb edir. MVVM-də yalnız ViewModel yaradılır — Activity-də abunə observe() vasitəsilə əl ilə detach() olmadan həyata keçirilir.
| Kriteriya | MVP | MVVM |
|---|---|---|
| ViewContract interfeysləri | 1 ekrana | Lazım deyil |
| Abunə idarəçiliyi | Əl ilə attach/detach | Avtomatik (lifecycle-aware) |
| Ekranın fırlanması | Retain-fragment | ViewModel sağ çıxır |
| Test etmə | Mock ViewContract | Asılılıqları olmayan təmiz sinif |
| Reaktivlik | Presenter-də callback-lər | LiveData/StateFlow/Combine |
MVVM-in çatışmazlıqları — reaktiv zəncirlərin debug edilməsinin mürəkkəbliyi və yanlış abunə zamanı yaddaş sızması riski. LiveData lifecycle-təhlükəsizliyi problemini həll edir, StateFlow lifecycleScope tələb edir, Combine — AnyCancellable ilə sink. MVP-də bütün çağırışlar aşkardır (view.showUser), MVVM-də məlumatlar reaktiv axın vasitəsilə gəlir — izləmə subscribe closure-ında debug breakpointləri tələb edir. Çoxsaylı StateFlow olan böyük ViewModel-lərdə View müəyyən Flow-a abunə deyilsə, UI yenilənməsini qaçırmaq olar.
MVP nə vaxt daha yaxşıdır — minimum Android versiyası API 21-dən (Android 5) aşağı olan layihələrdə, Jetpack ViewModel AndroidX olmadan əlçatan deyil, və Combine olmadan təmiz UIKit-də (iOS 12 və aşağı) layihələrdə. Bütün kod bazası artıq MVP-də olan legacy layihələr üçün MVVM-ə tam keçid həmişə əsaslandırılmır — 3 ayda 100 ekranı yenidən yazmaqdansa, tədricən məntiqi xidmətlərə çıxarmaqla MVP-ni saxlamaq daha ucuzdur.
ViewModel platforma asılılıqları olmadan unit-testlərlə test edilir — bu MVVM-in lehinə əsas arqumentdir. Android-də ViewModel Activity, Context və ya View ehtiva etmir — bütün asılılıqlar (Repository, UseCase) konstruktor vasitəsilə ötürülür və mock obyektləri ilə əvəz olunur. iOS-da ObservableObject tətbiqi işə salmadan XCTest vasitəsilə test edilir, bu da testlərin sabitliyini və sürətini təmin edir.
// MockK ilə Android ViewModel-in unit-testi
class UserViewModelTest {
private val repository = mockk<UserRepository>()
private val viewModel = UserViewModel(repository)
@Test
fun loadUser_success_updatesState() = runTest {
val user = User(1, "John", "john@test.com")
coEvery { repository.getUser(1) } returns Result.success(user)
viewModel.loadUser(1)
assertEquals(UserState.Success(user), viewModel.state.value)
}
@Test
fun loadUser_error_updatesErrorState() = runTest {
val error = RuntimeException("Network error")
coEvery { repository.getUser(1) } returns Result.failure(error)
viewModel.loadUser(1)
val state = viewModel.state.value
assertTrue(state is UserState.Error)
assertEquals("Network error", (state as UserState.Error).message)
}
}
iOS ViewModel analoji şəkildə test edilir: mock UserService yeridirik, loadUser çağırırıq, vəziyyəti XCTestExpectation vasitəsilə yoxlayırıq. Combine Publisher XCTestCase vasitəsilə wait(for: expectations, timeout: 1.0) ilə test edilir. UserState strukturu — associated values olan enum — əməliyyatdan sonra ekranın dəqiq vəziyyətini yoxlamağa imkan verir.
Kod əhatəsi IT Sectr-in MVVM-də layihələrində ViewModel və Repository üçün 75–90% təşkil edir. ViewModel unit-testlərlə, Repository test məlumat bazası ilə inteqrasiya testləri ilə əhatə olunur. SwiftUI və Jetpack Compose-da View kritik ssenarilər üçün UI testləri (XCUITest, Compose Test) ilə test edilir. Qalan UI snapshot testləri (Snapshot Testing) ilə yoxlanılır — bu UI testlərindən daha sürətlidir və göstərmənin düzgünlüyünə 95% əminlik verir.
Tez-tez verilən suallar
MVVM-də ViewModel-in View-ə istinadı yoxdur — məlumatlar reaktiv mexanizmlər (LiveData, StateFlow, @Published) vasitəsilə ötürülür. MVP-də Presenter birbaşa ViewContract interfeysi vasitəsilə View metodlarını çağırır. MVVM ViewContract və əl ilə attach/detach-i aradan qaldırır, lakin reaktiv axınları başa düşməyi tələb edir. ViewModel Android-də ekranın fırlanmasından sağ çıxır, Presenter retain-fragment tələb edir.
Minimum dəst: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx və ya kotlinx-coroutines-core (StateFlow). İnjeksiya üçün — Hilt və ya Koin. Asinxron əməliyyatlar üçün — Kotlin Coroutines. Mürəkkəb məlumat bağlaması üçün — DataBinding. Jetpack Compose-da (Google tərəfindən 2022-dən tövsiyə olunur) compose-runtime və lifecycle-viewmodel-compose kifayətdir.
SwiftUI (2019) reaktiv memarlıq üçün layihələndirilib: @State və @Published məlumat dəyişdikdə View-i avtomatik yenidən çəkir. MVVM SwiftUI-ə təbii uyğunluqdur: View — @ViewBuilder, ViewModel — ObservableObject. Apple MVVM-i yeganə pattern kimi təlqin etmir, lakin 2019-cu ildən bütün tədris materialları ViewModel + SwiftUI istifadə edir. UIKit üçün Apple MVC və ya Coordinator tövsiyə edir.
Android: viewModelScope ViewModel təmizlənərkən korutinləri avtomatik ləğv edir. iOS: Combine-dən AnyCancellable onu saxlayan obyekt azad edilərkən avtomatik abunədən çıxır. SwiftUI @StateObject həyat dövrünü avtomatik idarə edir. Əsas qaydalar: ViewModel-də View/Context istinadları saxlamamaq, təmizləmə zamanı uzunmüddətli əməliyyatları ləğv etmək, closure-larda weak self istifadə etmək.
StateFlow — müasir seçimdir. LiveData daha sadə və lifecycle-təhlükəsizdir, lakin StateFlow daha güclüdür: korutinlərlə işləyir, flatMap, combine, filter dəstəkləyir, @Nullable annotasiyası tələb etmir. LiveData-nın üstün olduğu yeganə ssenari — StateFlow-un (Kotlin Flow-API) əlçatan olmadığı Java kodu ilə işləməkdir. Google Kotlin-də yeni layihələr üçün StateFlow tövsiyə edir.
Nəticə
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.
Həm də oxuyun