MVVM (Model-View-ViewModel) — pola arsitektur di mana ViewModel menggantikan Presenter dan menggunakan mekanisme reaktif untuk berkomunikasi dengan View: ObservableObject di SwiftUI, LiveData/StateFlow di Android. ViewModel tidak memiliki referensi ke View — data dikirim melalui langganan, yang menghilangkan kebutuhan akan antarmuka ViewContract dan membuat pengujian lebih sederhana. Apple merekomendasikan MVVM dengan SwiftUI sejak 2019, Google — MVVM dengan Jetpack sebagai arsitektur resmi Android. Selengkapnya di Android Architecture Guide.
Utama
MVVM (Model-View-ViewModel) — pola arsitektur yang dijelaskan oleh John Gossman pada tahun 2005 untuk Windows Presentation Foundation (WPF) dari Microsoft. ViewModel — komponen sentral yang berisi status layar dan logika bisnis, tetapi tidak memiliki referensi ke View. Data dikirim melalui mekanisme pengikatan reaktif: View berlangganan perubahan ViewModel dan secara otomatis digambar ulang saat data berubah.
Perbedaan utama MVVM dari MVP — tidak adanya ViewContract. Di MVP, Presenter memanggil metode view.showUser(data), artinya Presenter secara aktif “mendorong” data ke View. Di MVVM, View sendiri “menarik” data dari ViewModel melalui langganan: ViewModel tidak tahu apakah ia memiliki pelanggan. Ini menghilangkan masalah View yang terlepas — jika Activity dihancurkan saat rotasi, ViewModel terus bekerja, dan Activity baru cukup berlangganan data terkini. Di IT Sectr kami menggunakan MVVM di semua proyek baru sejak 2020 — kode menjadi lebih dapat diprediksi, pengujian lebih stabil.
| Komponen | Tanggung Jawab | Platform |
|---|---|---|
| Model | Data, logika bisnis, repositori | Android/iOS |
| View | Tampilan, langganan ke ViewModel | Activity/Composable, UIView/SwiftUI View |
| ViewModel | Status layar, logika, navigasi | ViewModel (Jetpack), ObservableObject |
Pengikatan reaktif — fondasi MVVM. Di Android, LiveData (bagian dari Jetpack) adalah penyimpanan data yang dapat diamati. Activity berlangganan melalui observe(): viewModel.user.observe(this) { user -> binding.name.text = user.name }. Saat user berubah, semua pelanggan secara otomatis menerima nilai baru. Di iOS, SwiftUI menggunakan properti @Published di ViewModel — perubahan secara otomatis menggambar ulang View. Ini menghilangkan panggilan manual showUser/hideLoading yang diperlukan di MVP.
ViewModel dari Jetpack — komponen resmi dari Google untuk mengimplementasikan MVVM. ViewModel bertahan dari rotasi layar: saat konfigurasi berubah, Activity dihancurkan dan dibuat ulang, sementara ViewModel tetap di memori. Instance Activity baru menerima ViewModel yang sama melalui ViewModelProvider. ViewModel tidak memiliki referensi ke Activity, Context atau View — murni dan diuji dengan unit-test tanpa Robolectric.
// ViewModel dengan StateFlow — implementasi modern MVVM
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) berlangganan state
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 -> /* tampilkan loading */
is UserState.Success -> /* tampilkan data */
is UserState.Error -> /* tampilkan kesalahan */
}
}.launchIn(lifecycleScope)
viewModel.loadUser(42)
}
}
LiveData vs StateFlow — LiveData (2017) — komponen reaktif pertama Jetpack, dioptimalkan untuk siklus hidup Activity: berhenti berlangganan otomatis saat onStop. StateFlow (2021) — implementasi Kotlin Flow, tidak terikat pada siklus hidup, tetapi memerlukan penghentian langganan manual melalui lifecycleScope. StateFlow mendukung coroutines, concat, map dan operator Flow lainnya yang tidak ada di LiveData. Di IT Sectr kami menggunakan StateFlow untuk semua ViewModel baru — lebih pendek, lebih kuat, dan terintegrasi lebih baik dengan coroutine.
DataBinding dan ViewBinding — DataBinding menghubungkan ViewModel dengan XML melalui @{viewModel.user.name} langsung di tata letak, menghilangkan kode di Activity. ViewBinding menghasilkan kelas type-safe untuk akses ke View. Google merekomendasikan ViewBinding untuk proyek sederhana dan DataBinding untuk proyek dengan pengikatan data yang kompleks. Di Jetpack Compose, DataBinding tidak diperlukan — fungsi @Composable secara otomatis digambar ulang saat State berubah.
MVVM di iOS diimplementasikan melalui ObservableObject dari Combine. ViewModel — kelas yang mewarisi ObservableObject, dengan bidang @Published. SwiftUI View berlangganan ke ViewModel melalui @ObservedObject atau @StateObject. Saat properti @Published berubah, SwiftUI secara otomatis menggambar ulang View yang bergantung pada properti ini. Apple memperkenalkan SwiftUI pada tahun 2019 di WWDC bersama dengan Combine — sejak saat itu MVVM menjadi pola yang direkomendasikan secara resmi untuk iOS.
import SwiftUI
import Combine
// ViewModel — ObservableObject dengan bidang @Published
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 — berlangganan ke ViewModel
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 membuat ViewModel dan mengelola siklus hidupnya (sekali selama masa hidup View). @ObservedObject — ViewModel dibuat dari luar dan diteruskan ke View. WWDC 2022 merekomendasikan @StateObject untuk pembuatan dan @ObservedObject untuk meneruskan ViewModel antar Views. Di iOS 17 (2023) muncul @Observable — makro yang mengotomatiskan langganan dan menghilangkan anotasi @Published. @Observable — evolusi Combine, yang mendekatkan pengembangan iOS ke reaktivitas Kotlin Flow.
UIKit + MVVM — untuk proyek di UIKit (tanpa SwiftUI), MVVM diimplementasikan melalui Combine dan @Published dengan langganan di UIViewController melalui sink(). ViewModel sama, View — UIViewController dengan langganan ke @Published. Combine tersedia sejak iOS 13 (2019) dan terintegrasi dalam sistem — tidak memerlukan dependensi tambahan. Menurut Apple Developer Survey (2025), 45% proyek iOS menggunakan Combine bahkan dengan UIKit, 35% menggunakan SwiftUI + Combine, 20% — RxSwift (legacy).
MVVM unggul dari MVP dalam tiga aspek utama: tidak adanya antarmuka ViewContract, manajemen langganan otomatis, dan bertahan dari rotasi layar. Di MVP, setiap layar memerlukan antarmuka ViewContract + kelas Presenter + langganan/berhenti langganan di onStart/onStop. Di MVVM hanya ViewModel yang dibuat — langganan di Activity dilakukan melalui observe() tanpa detach manual.
| Kriteria | MVP | MVVM |
|---|---|---|
| Antarmuka ViewContract | 1 per layar | Tidak diperlukan |
| Manajemen langganan | Manual attach/detach | Otomatis (lifecycle-aware) |
| Rotasi layar | Retain-fragment | ViewModel bertahan |
| Pengujian | Mock ViewContract | Kelas murni tanpa dependensi |
| Reaktivitas | Callback di Presenter | LiveData/StateFlow/Combine |
Kekurangan MVVM — kompleksitas debugging rantai reaktif dan risiko kebocoran memori saat langganan yang tidak tepat. LiveData memecahkan masalah keamanan siklus hidup, StateFlow memerlukan lifecycleScope, Combine — sink dengan AnyCancellable. Di MVP semua panggilan eksplisit (view.showUser), di MVVM data datang melalui aliran reaktif — pelacakan memerlukan breakpoint debug di closure subscribe. Di ViewModel besar dengan banyak StateFlow, pembaruan UI dapat terlewatkan jika View tidak berlangganan ke Flow tertentu.
Kapan MVP masih lebih baik — dalam proyek dengan versi minimum Android di bawah API 21 (Android 5), di mana Jetpack ViewModel tidak tersedia tanpa AndroidX, dan dalam proyek di UIKit murni tanpa Combine (iOS 12 ke bawah). Untuk proyek legacy di mana seluruh basis kode sudah di MVP, transisi penuh ke MVVM tidak selalu dibenarkan — lebih murah mempertahankan MVP dengan pemindahan logika bertahap ke layanan, daripada menulis ulang 100 layar dalam 3 bulan.
ViewModel diuji dengan unit-test tanpa dependensi platform — ini adalah argumen utama untuk MVVM. Di Android, ViewModel tidak berisi Activity, Context atau View — semua dependensi (Repository, UseCase) diteruskan melalui konstruktor dan diganti dengan objek mock. Di iOS, ObservableObject diuji melalui XCTest tanpa menjalankan aplikasi, yang memberikan stabilitas dan kecepatan eksekusi pengujian.
// Unit-test Android ViewModel dengan MockK
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 diuji dengan cara serupa: kita menyuntikkan mock UserService, memanggil loadUser, memeriksa status melalui XCTestExpectation. Combine Publisher diuji melalui XCTestCase dengan wait(for: expectations, timeout: 1.0). Struktur UserState — enum dengan associated values — memungkinkan pemeriksaan status layar yang tepat setelah operasi.
Cakupan kode di proyek IT Sectr pada MVVM adalah 75-90% untuk ViewModel dan Repository. ViewModel dicakup oleh unit-test, Repository — oleh pengujian integrasi dengan database pengujian. View di SwiftUI dan Jetpack Compose diuji dengan pengujian UI (XCUITest, Compose Test) untuk skenario kritis. Sisa UI diperiksa dengan pengujian tangkapan layar (Snapshot Testing) — ini lebih cepat daripada pengujian UI dan memberikan 95% keyakinan akan kebenaran tampilan.
Pertanyaan yang Sering Diajukan
Di MVVM, ViewModel tidak memiliki referensi ke View — data dikirim melalui mekanisme reaktif (LiveData, StateFlow, @Published). Di MVP, Presenter langsung memanggil metode View melalui antarmuka ViewContract. MVVM menghilangkan ViewContract dan attach/detach manual, tetapi memerlukan pemahaman tentang aliran reaktif. ViewModel bertahan dari rotasi layar di Android, Presenter memerlukan retain-fragment.
Set minimum: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx atau kotlinx-coroutines-core (StateFlow). Untuk injeksi — Hilt atau Koin. Untuk operasi asinkron — Kotlin Coroutines. Untuk pengikatan data yang kompleks — DataBinding. Di Jetpack Compose (direkomendasikan oleh Google sejak 2022) compose-runtime dan lifecycle-viewmodel-compose sudah cukup.
SwiftUI (2019) dirancang untuk arsitektur reaktif: @State dan @Published secara otomatis menggambar ulang View saat data berubah. MVVM — kecocokan alami untuk SwiftUI: View — @ViewBuilder, ViewModel — ObservableObject. Apple tidak memaksakan MVVM sebagai satu-satunya pola, tetapi semua materi pelatihan sejak 2019 menggunakan ViewModel + SwiftUI. Untuk UIKit, Apple merekomendasikan MVC atau Coordinator.
Android: viewModelScope secara otomatis membatalkan coroutine saat pembersihan ViewModel. iOS: AnyCancellable dari Combine secara otomatis berhenti berlangganan saat objek yang menyimpannya dibebaskan. SwiftUI @StateObject mengelola siklus hidup secara otomatis. Aturan utama: jangan menyimpan referensi ke View/Context di ViewModel, batalkan operasi berjalan lama saat pembersihan, gunakan weak self di closure.
StateFlow — pilihan modern. LiveData lebih sederhana dan aman untuk siklus hidup, tetapi StateFlow lebih kuat: bekerja dengan coroutine, mendukung flatMap, combine, filter, tidak memerlukan anotasi @Nullable. Satu-satunya skenario di mana LiveData lebih disukai — bekerja dengan kode Java, di mana StateFlow (Kotlin Flow-API) tidak tersedia. Google merekomendasikan StateFlow untuk proyek baru di Kotlin.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga