MVVM: nedir, mobil geliştirmede Model-View-ViewModel deseni

Yazar: IT Sectr Yayınlanma: 2026-02-16 Okuma süresi: 9 dk

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 (veri), View (arayüz), ViewModel (View'a referans olmadan durum ve mantık)
  • Reaktif bağlama — LiveData, StateFlow, ObservableObject veri değiştiğinde UI'yı otomatik olarak günceller
  • ViewModel — ekran dönüşünden etkilenmez ve Android SDK/UIKit'e bağımlı değildir, birim testlerle test edilebilir
  • Android Jetpack — ViewModel, LiveData, DataBinding — MVVM için Google'ın resmi yığını
  • SwiftUI + Combine — iOS'ta @Published ve @ObservedObject ile yerel MVVM uygulaması

MVVM Nedir: Model-View-ViewModel Deseninin Özü

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şenSorumlulukPlatform
ModelVeri, iş mantığı, depolarAndroid/iOS
ViewGörüntüleme, ViewModel'e abonelikActivity/Composable, UIView/SwiftUI View
ViewModelEkran durumu, mantık, navigasyonViewModel (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.

Android'de MVVM: ViewModel, LiveData ve StateFlow

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.

kotlin
// 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: ObservableObject ve SwiftUI

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.

swift
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 ile MVP Karşılaştırması: Avantajlar ve Dezavantajlar

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.

KriterMVPMVVM
ViewContract ArayüzleriEkran başına 1Gerekli değil
Abonelik YönetimiManuel attach/detachOtomatik (lifecycle-aware)
Ekran DönüşüRetain-fragmentViewModel dönüşten etkilenmez
TestMock ViewContractBağımlılıksız temiz sınıf
ReaktivitePresenter'da geri çağrılarLiveData/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.

Android ve iOS'ta ViewModel Testi

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.

kotlin
// 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 ve MVP arasındaki temel fark nedir?

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.

Android'de MVVM için hangi kütüphaneler gereklidir?

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.

Apple neden iOS için MVVM'yi öneriyor?

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.

ViewModel'de bellek sızıntıları nasıl önlenir?

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.

Android için LiveData mı StateFlow mu seçilmeli?

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

  • MVVM (Model-View-ViewModel) — ViewModel'in View'a referansı olmayan reaktif desen
  • ViewModel — ekran dönüşünden etkilenmez, birim testlerle test edilebilir, UI'dan bağımsız
  • Android — ViewModel + StateFlow + Kotlin Coroutines — Google'ın modern yığını
  • iOS — ObservableObject + @Published + SwiftUI — yerel MVVM uygulaması
  • MVVM vs MVP — MVVM, ViewContract ve manuel attach/detach'ı ortadan kaldırır
  • Test — ViewModel, platform bağımlılıkları olmadan birim testlerle kapsanır
  • Öneri — Yeni projeler için MVVM; eski destek için MVP

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.

Projeyi tartış

Ayrıca okuyun