MVVM (Model-View-ViewModel), ViewModel'in Presenter'ın yerini aldığı ve View ile iletişim için reaktif mekanizmalar kullanan bir mimari desendir: SwiftUI'da ObservableObject, Android'de LiveData/StateFlow. ViewModel'in View'a referansı yoktur — veriler abonelik yoluyla iletilir, bu da ViewContract arayüzlerine olan ihtiyacı ortadan kaldırır ve test etmeyi daha da basitleştirir. Apple, 2019'dan beri SwiftUI ile MVVM'yi önermektedir, Google ise Jetpack ile MVVM'yi resmi Android mimarisi olarak önermektedir. Daha fazla bilgi için Android Architecture Guide'a bakın.
Önemli Noktalar
MVVM (Model-View-ViewModel), John Gossman tarafından 2005 yılında Microsoft'un Windows Presentation Foundation (WPF)'ı için tanımlanan bir mimari desendir. ViewModel, ekran durumunu ve iş mantığını içeren ancak View'a referansı olmayan merkezi bileşendir. Veriler reaktif bağlama mekanizmaları aracılığıyla iletilir: View, ViewModel'deki değişikliklere abone olur ve veriler değiştiğinde otomatik olarak yeniden oluşturulur.
MVVM ve MVP Arasındaki Temel Fark — ViewContract'ın olmamasıdır. MVP'de Presenter, view.showUser(data) yöntemlerini çağırır, yani Presenter verileri aktif olarak View'a "iter". MVVM'de View'ın kendisi abonelik yoluyla ViewModel'den verileri "çeker": ViewModel'in bir abonesi olup olmadığını bilmez. Bu, bağlantısı kesilmiş View sorununu ortadan kaldırır — döndürme sırasında Activity yok edilirse, ViewModel çalışmaya devam eder ve yeni Activity mevcut verilere abone olur. IT Sectr'de, 2020'den beri tüm yeni projelerde MVVM kullanıyoruz — kod daha öngörülebilir hale geldi, testler daha kararlı hale geldi.
| Bileşen | Sorumluluk | Platform |
|---|---|---|
| Model | Veri, iş mantığı, depolar | Android/iOS |
| View | Görüntüleme, ViewModel'e abonelik | Activity/Composable, UIView/SwiftUI View |
| ViewModel | Ekran durumu, mantık, navigasyon | ViewModel (Jetpack), ObservableObject |
Reaktif bağlama — MVVM'nin temelidir. Android'de LiveData (Jetpack'in bir parçası) gözlemlenebilir bir veri tutucudur. Activity, observe() aracılığıyla abone olur: viewModel.user.observe(this) { user -> binding.name.text = user.name }. user değiştiğinde, tüm aboneler otomatik olarak yeni değeri alır. iOS'ta SwiftUI, ViewModel'de @Published özelliklerini kullanır — değişiklikler otomatik olarak View'ı yeniden oluşturur. Bu, MVP'de gerekli olan manuel showUser/hideLoading çağrılarını ortadan kaldırır.
Jetpack'ten ViewModel — MVVM'yi uygulamak için Google'ın resmi bileşenidir. ViewModel ekran dönüşünden etkilenmez: yapılandırma değiştiğinde Activity yok edilir ve yeniden oluşturulur, ancak ViewModel bellekte kalır. Yeni Activity örneği, ViewModelProvider aracılığıyla aynı ViewModel'i alır. ViewModel'in Activity, Context veya View'a referansı yoktur — temizdir ve Robolectric olmadan birim testlerle test edilebilir.
// StateFlow ile ViewModel — modern MVVM uygulaması
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'e abone 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üklemeyi göster */
is UserState.Success -> /* verileri göster */
is UserState.Error -> /* hatayı göster */
}
}.launchIn(lifecycleScope)
viewModel.loadUser(42)
}
}
LiveData vs StateFlow — LiveData (2017) — Jetpack'in ilk reaktif bileşeni, Activity yaşam döngüsü için optimize edilmiştir: onStop'ta otomatik abonelik iptali. StateFlow (2021) — Kotlin Flow uygulaması, yaşam döngüsüne bağlı değildir ancak lifecycleScope aracılığıyla manuel abonelik iptali gerektirir. StateFlow, LiveData'da bulunmayan coroutines, concat, map ve diğer Flow operatörlerini destekler. IT Sectr'de, tüm yeni ViewModel'ler için StateFlow kullanıyoruz — daha kısa, daha güçlü ve coroutines ile daha iyi entegre olur.
DataBinding ve ViewBinding — DataBinding, ViewModel'i XML'e @{viewModel.user.name} ile doğrudan düzende bağlayarak Activity'deki kodu ortadan kaldırır. ViewBinding, görünümlere erişmek için tür güvenli bir sınıf oluşturur. Google, basit projeler için ViewBinding'i ve karmaşık veri bağlaması olan projeler için DataBinding'i önerir. Jetpack Compose'ta DataBinding gerekli değildir — @Composable işlevleri State değiştiğinde otomatik olarak yeniden oluşturulur.
iOS'ta MVVM, Combine'dan ObservableObject aracılığıyla uygulanır. ViewModel, @Published özelliklerine sahip, ObservableObject'tan miras alan bir sınıftır. SwiftUI View, @ObservedObject veya @StateObject aracılığıyla ViewModel'e abone olur. Bir @Published özelliği değiştiğinde, SwiftUI bu özelliğe bağımlı olan View'ı otomatik olarak yeniden oluşturur. Apple, 2019'da WWDC'de SwiftUI'yı Combine ile birlikte tanıttı — o zamandan beri MVVM, iOS için resmi olarak önerilen desen haline geldi.
import SwiftUI
import Combine
// ViewModel — @Published alanlarıyla 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'e abone 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'i oluşturur ve yaşam döngüsünü yönetir (View ömrü boyunca bir kez). @ObservedObject — ViewModel harici olarak oluşturulur ve View'a iletilir. WWDC 2022, oluşturma için @StateObject'i ve ViewModel'i Views arasında iletmek için @ObservedObject'i önerir. iOS 17'de (2023), @Observable makrosu ortaya çıktı — aboneliği otomatikleştirir ve @Published ek açıklamalarını ortadan kaldırır. @Observable, Combine'ın evrimidir ve iOS geliştirmeyi Kotlin Flow reaktivitesine yaklaştırır.
UIKit + MVVM — UIKit projeleri için (SwiftUI olmadan), MVVM, Combine ve @Published kullanılarak UIViewController'da sink() aracılığıyla abonelikle uygulanır. ViewModel aynıdır, View @Published'a abonelikleri olan UIViewController'dır. Combine, iOS 13'ten (2019) beri kullanılabilir ve sisteme entegredir — ek bağımlılık gerektirmez. Apple Developer Survey'e (2025) göre, iOS projelerinin %45'i UIKit ile bile Combine kullanıyor, %35'i SwiftUI + Combine kullanıyor, %20'si RxSwift (eski) kullanıyor.
MVVM, MVP'ye karşı üstündür üç temel açıdan: ViewContract arayüzlerinin olmaması, otomatik abonelik yönetimi ve ekran dönüşünden etkilenmeme. MVP'de her ekran bir ViewContract arayüzü + Presenter sınıfı + onStart/onStop'ta abonelik/iptal gerektirir. MVVM'de yalnızca ViewModel oluşturulur — Activity'deki abonelik manuel detach() olmadan observe() aracılığıyla yapılır.
| Kriter | MVP | MVVM |
|---|---|---|
| ViewContract Arayüzleri | Ekran başına 1 | Gerekli değil |
| Abonelik Yönetimi | Manuel attach/detach | Otomatik (lifecycle-aware) |
| Ekran Dönüşü | Retain-fragment | ViewModel dönüşten etkilenmez |
| Test | Mock ViewContract | Bağımlılıksız temiz sınıf |
| Reaktivite | Presenter'da geri çağrılar | LiveData/StateFlow/Combine |
MVVM'nin Dezavantajları — reaktif zincirlerde hata ayıklamanın karmaşıklığı ve yanlış abonelikte bellek sızıntısı riski. LiveData yaşam döngüsü güvenliğini çözer, StateFlow lifecycleScope gerektirir, Combine AnyCancellable ile sink gerektirir. MVP'de tüm çağrılar açıktır (view.showUser), MVVM'de veriler reaktif bir akışla gelir — izleme, subscribe closure'larında hata ayıklama kesme noktaları gerektirir. Birden çok StateFlow'a sahip büyük ViewModel'lerde, View belirli bir Flow'a abone değilse UI güncellemesi kaçırılabilir.
MVP'nin Hala Daha İyi Olduğu Durumlar — minimum Android sürümü API 21'in (Android 5) altında olan projelerde (Jetpack ViewModel AndroidX olmadan kullanılamaz) ve Combine olmadan saf UIKit (iOS 12 ve altı) kullanan projelerde. Kod tabanının tamamı zaten MVP olan eski projeler için, MVVM'ye tam geçiş her zaman haklı değildir — 3 ayda 100 ekranı yeniden yazmaktansa, mantığı hizmetlere kademeli olarak çıkararak MVP'yi sürdürmek daha ucuzdur.
ViewModel, platform bağımlılıkları olmadan birim testlerle test edilir — bu MVVM lehindeki ana argümandır. Android'de ViewModel, Activity, Context veya View içermez — tüm bağımlılıklar (Repository, UseCase) kurucu aracılığıyla iletilir ve mock nesnelerle değiştirilir. iOS'ta ObservableObject, uygulama başlatılmadan XCTest aracılığıyla test edilir, bu da istikrar ve test yürütme hızı sağlar.
// MockK ile Android ViewModel birim 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 benzer şekilde test edilir: mock UserService enjekte edin, loadUser'ı çağırın, XCTestExpectation aracılığıyla durumu kontrol edin. Combine Publisher, wait(for: expectations, timeout: 1.0) ile XCTestCase aracılığıyla test edilir. UserState yapısı — ilişkili değerlere sahip enum — bir işlemden sonra ekranın tam durumunu kontrol etmeye olanak tanır.
Kod kapsamı MVVM kullanan IT Sectr projelerinde ViewModel ve Repository için %75–90'dır. ViewModel birim testlerle, Repository test veritabanıyla entegrasyon testleriyle kapsanır. SwiftUI ve Jetpack Compose'ta View, kritik senaryolar için UI testleriyle (XCUITest, Compose Test) test edilir. UI'nın geri kalanı ekran görüntüsü testleriyle (Snapshot Testing) kontrol edilir — bu UI testlerinden daha hızlıdır ve görüntüleme doğruluğunda %95 güven sağlar.
Sıkça Sorulan Sorular
MVVM'de ViewModel'in View'a referansı yoktur — veriler reaktif mekanizmalar (LiveData, StateFlow, @Published) aracılığıyla iletilir. MVP'de Presenter, ViewContract arayüzü aracılığıyla doğrudan View yöntemlerini çağırır. MVVM, ViewContract'ı ve manuel attach/detach'ı ortadan kaldırır ancak reaktif akışların anlaşılmasını gerektirir. ViewModel Android'de ekran dönüşünden etkilenmez, Presenter bir retain-fragment gerektirir.
Minimum set: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx veya kotlinx-coroutines-core (StateFlow). Enjeksiyon için — Hilt veya Koin. Asenkron işlemler için — Kotlin Coroutines. Karmaşık veri bağlama için — DataBinding. Jetpack Compose'ta (2022'den beri Google tarafından önerilir), compose-runtime ve lifecycle-viewmodel-compose yeterlidir.
SwiftUI (2019) reaktif mimari için tasarlanmıştır: @State ve @Published, veriler değiştiğinde otomatik olarak View'ı yeniden oluşturur. MVVM, SwiftUI için doğal bir uyumdur: View — @ViewBuilder, ViewModel — ObservableObject. Apple MVVM'yi tek desen olarak dayatmaz, ancak 2019'dan beri tüm eğitim materyalleri ViewModel + SwiftUI kullanır. UIKit için Apple, MVC veya Coordinator'ı önerir.
Android: viewModelScope, ViewModel temizlenirken coroutines'leri otomatik olarak iptal eder. iOS: Combine'dan AnyCancellable, onu tutan nesne serbest bırakıldığında aboneliği otomatik olarak iptal eder. SwiftUI @StateObject yaşam döngüsünü otomatik olarak yönetir. Ana kurallar: ViewModel'de View/Context referanslarını saklamayın, temizlik sırasında uzun süreli işlemleri iptal edin, closure'larda weak self kullanın.
StateFlow modern seçimdir. LiveData daha basit ve yaşam döngüsü güvenlidir, ancak StateFlow daha güçlüdür: coroutines ile çalışır, flatMap, combine, filter'ı destekler, @Nullable ek açıklaması gerektirmez. LiveData'nın tercih edildiği tek senaryo — StateFlow'un (Kotlin Flow API) mevcut olmadığı Java koduyla çalışmaktır. Google, yeni Kotlin projeleri için StateFlow'u önerir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun