MVVM (Model-View-ViewModel) — isang pattern ng arkitektura kung saan pinapalitan ng ViewModel ang Presenter at gumagamit ng mga reaktibong mekanismo para sa komunikasyon sa View: ObservableObject sa SwiftUI, LiveData/StateFlow sa Android. Ang ViewModel ay walang referensya sa View — ang data ay ipinapadala sa pamamagitan ng subscription, na nag-aalis ng pangangailangan para sa mga interface ng ViewContract at ginagawang mas simple ang pag-test. Inirerekomenda ng Apple ang MVVM gamit ang SwiftUI mula noong 2019, Google — MVVM gamit ang Jetpack bilang opisyal na arkitektura ng Android. Higit pa sa Android Architecture Guide.
Mga Pangunahing Punto
MVVM (Model-View-ViewModel) — isang pattern ng arkitektura na inilarawan ni John Gossman noong 2005 para sa Windows Presentation Foundation (WPF) ng Microsoft. Ang ViewModel — ang sentral na komponent na naglalaman ng estado ng screen at lohika ng negosyo, ngunit walang referensya sa View. Ang data ay ipinapadala sa pamamagitan ng mga mekanismo ng reaktibong pagbubuklod: ang View ay nag-subscribe sa mga pagbabago ng ViewModel at awtomatikong iginuguhit muli kapag nagbago ang data.
Pangunahing pagkakaiba ng MVVM mula sa MVP — kawalan ng ViewContract. Sa MVP, ang Presenter ay tumatawag ng mga pamamaraan ng view.showUser(data), ibig sabihin, aktibong “itinutulak” ng Presenter ang data sa View. Sa MVVM, ang View mismo ay “humihila” ng data mula sa ViewModel sa pamamagitan ng subscription: hindi alam ng ViewModel kung may subscriber ito. Inaalis nito ang problema ng hiwalay na View — kung ang Activity ay nawasak sa pag-ikot, ang ViewModel ay patuloy na gumagana, at ang bagong Activity ay nag-subscribe lamang sa kasalukuyang data. Sa IT Sectr ginagamit namin ang MVVM sa lahat ng bagong proyekto mula noong 2020 — ang code ay naging mas prediktibo, ang mga test ay mas matatag.
| Komponent | Responsibilidad | Platform |
|---|---|---|
| Model | Data, lohika ng negosyo, repositoryo | Android/iOS |
| View | Pagpapakita, subscription sa ViewModel | Activity/Composable, UIView/SwiftUI View |
| ViewModel | Estado ng screen, lohika, nabigasyon | ViewModel (Jetpack), ObservableObject |
Reaktibong pagbubuklod — pundasyon ng MVVM. Sa Android, ang LiveData (bahagi ng Jetpack) ay isang nakikitang imbakan ng data. Ang Activity ay nag-subscribe sa pamamagitan ng observe(): viewModel.user.observe(this) { user -> binding.name.text = user.name }. Kapag nagbago ang user, lahat ng subscriber ay awtomatikong nakakatanggap ng bagong halaga. Sa iOS, ang SwiftUI ay gumagamit ng @Published na mga katangian sa ViewModel — ang mga pagbabago ay awtomatikong iginuguhit muli ang View. Inaalis nito ang mga manu-manong tawag na showUser/hideLoading na kinakailangan sa MVP.
ViewModel mula sa Jetpack — ang opisyal na komponent mula sa Google para sa pagpapatupad ng MVVM. Ang ViewModel ay nakakaligtas sa pag-ikot ng screen: kapag nagbago ang configuration, ang Activity ay nawasak at muling nilikha, habang ang ViewModel ay nananatili sa memorya. Ang bagong instance ng Activity ay tumatanggap ng parehong ViewModel sa pamamagitan ng ViewModelProvider. Ang ViewModel ay walang referensya sa Activity, Context o View — ito ay dalisay at sinusuri ng unit-test nang walang Robolectric.
// ViewModel na may StateFlow — modernong implementasyon ng 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) ay nag-subscribe sa 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 -> /* ipakita ang loading */
is UserState.Success -> /* ipakita ang data */
is UserState.Error -> /* ipakita ang error */
}
}.launchIn(lifecycleScope)
viewModel.loadUser(42)
}
}
LiveData vs StateFlow — LiveData (2017) — unang reaktibong komponent ng Jetpack, na-optimize para sa lifecycle ng Activity: awtomatikong pag-unsubscribe sa onStop. StateFlow (2021) — implementasyon ng Kotlin Flow, hindi nakatali sa lifecycle, ngunit nangangailangan ng manu-manong pag-unsubscribe sa pamamagitan ng lifecycleScope. Sinusuportahan ng StateFlow ang coroutines, concat, map at iba pang Flow operator na wala sa LiveData. Sa IT Sectr ginagamit namin ang StateFlow para sa lahat ng bagong ViewModel — ito ay mas maikli, mas malakas, at mas mahusay na naka-integrate sa coroutine.
DataBinding at ViewBinding — ang DataBinding ay nagbubuklod ng ViewModel sa XML sa pamamagitan ng @{viewModel.user.name} nang direkta sa layout, inaalis ang code sa Activity. Ang ViewBinding ay bumubuo ng type-safe na klase para sa pag-access sa View. Inirerekomenda ng Google ang ViewBinding para sa mga simpleng proyekto at DataBinding para sa mga proyektong may komplikadong pagbubuklod ng data. Sa Jetpack Compose, hindi kailangan ang DataBinding — ang mga @Composable function ay awtomatikong iginuguhit muli kapag nagbago ang State.
MVVM sa iOS ay ipinapatupad sa pamamagitan ng ObservableObject mula sa Combine. Ang ViewModel — isang klase na nagmamana ng ObservableObject, na may @Published na mga field. Ang SwiftUI View ay nag-subscribe sa ViewModel sa pamamagitan ng @ObservedObject o @StateObject. Kapag nagbago ang @Published na katangian, awtomatikong iginuguhit muli ng SwiftUI ang View na nakadepende sa katangiang ito. Ipinakilala ng Apple ang SwiftUI noong 2019 sa WWDC kasama ang Combine — mula sa sandaling iyon, ang MVVM ay naging opisyal na inirerekomendang pattern para sa iOS.
import SwiftUI
import Combine
// ViewModel — ObservableObject na may @Published na field
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 — nag-subscribe sa 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 — ang @StateObject ay lumilikha ng ViewModel at namamahala sa lifecycle nito (isang beses sa buhay ng View). @ObservedObject — ang ViewModel ay nilikha sa labas at ipinapasa sa View. Inirerekomenda ng WWDC 2022 ang @StateObject para sa paglikha at @ObservedObject para sa pagpasa ng ViewModel sa pagitan ng Views. Sa iOS 17 (2023) lumitaw ang @Observable — isang macro na nag-automate ng subscription at nag-aalis ng @Published na mga anotasyon. Ang @Observable — ebolusyon ng Combine, na naglalapit sa iOS development sa reaktibidad ng Kotlin Flow.
UIKit + MVVM — para sa mga proyekto sa UIKit (walang SwiftUI), ang MVVM ay ipinapatupad sa pamamagitan ng Combine at @Published na may subscription sa UIViewController sa pamamagitan ng sink(). Ang ViewModel ay pareho, ang View — UIViewController na may mga subscription sa @Published. Ang Combine ay available mula noong iOS 13 (2019) at naka-embed sa system — hindi nangangailangan ng karagdagang dependencies. Ayon sa Apple Developer Survey (2025), 45% ng mga iOS na proyekto ay gumagamit ng Combine kahit sa UIKit, 35% ay gumagamit ng SwiftUI + Combine, 20% — RxSwift (legacy).
Ang MVVM ay nananalo sa MVP sa tatlong pangunahing aspeto: kawalan ng mga interface ng ViewContract, awtomatikong pamamahala ng subscription, at pagtitiis sa pag-ikot ng screen. Sa MVP, bawat screen ay nangangailangan ng interface ng ViewContract + klase ng Presenter + subscription/unsubscribe sa onStart/onStop. Sa MVVM, tanging ViewModel ang nilikha — ang subscription sa Activity ay ginagawa sa pamamagitan ng observe() nang walang manu-manong detach().
| Kriterya | MVP | MVVM |
|---|---|---|
| Mga interface ng ViewContract | 1 bawat screen | Hindi kailangan |
| Pamamahala ng subscription | Manu-manong attach/detach | Awtomatiko (lifecycle-aware) |
| Pag-ikot ng screen | Retain-fragment | ViewModel ay nakakaligtas |
| Pag-test | Mock ViewContract | Dalisay na klase walang dependencies |
| Reaktibidad | Callback sa Presenter | LiveData/StateFlow/Combine |
Mga kahinaan ng MVVM — komplikasyon ng pag-debug ng mga reaktibong chain at panganib ng memory leak sa hindi tamang subscription. Ang LiveData ay lumulutas ng problema sa kaligtasan ng lifecycle, ang StateFlow ay nangangailangan ng lifecycleScope, ang Combine — sink na may AnyCancellable. Sa MVP, lahat ng tawag ay malinaw (view.showUser), sa MVVM ang data ay dumarating sa pamamagitan ng reaktibong daloy — ang pagsubaybay ay nangangailangan ng debug-breakpoints sa subscribe closure. Sa malalaking ViewModel na may maraming StateFlow, maaaring makaligtaan ang pag-update ng UI kung ang View ay hindi naka-subscribe sa partikular na Flow.
Kailan mas mahusay pa rin ang MVP — sa mga proyektong may minimum na bersyon ng Android sa ibaba ng API 21 (Android 5), kung saan ang Jetpack ViewModel ay hindi available nang walang AndroidX, at sa mga proyekto sa purong UIKit na walang Combine (iOS 12 at mas mababa). Para sa mga legacy na proyekto kung saan ang buong codebase ay nasa MVP na, ang buong paglipat sa MVVM ay hindi palaging makatwiran — mas mura ang panatilihin ang MVP na may unti-unting paglipat ng lohika sa mga serbisyo, kaysa muling isulat ang 100 screen sa loob ng 3 buwan.
Ang ViewModel ay sinusuri ng unit-test nang walang mga dependency sa platform — ito ang pangunahing argumento para sa MVVM. Sa Android, ang ViewModel ay hindi naglalaman ng Activity, Context o View — lahat ng dependencies (Repository, UseCase) ay ipinapasa sa pamamagitan ng constructor at pinapalitan ng mga mock object. Sa iOS, ang ObservableObject ay sinusuri sa pamamagitan ng XCTest nang hindi pinapatakbo ang application, na nagbibigay ng katatagan at bilis ng pagpapatupad ng mga test.
// Unit-test ng Android ViewModel gamit ang 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 ay sinusuri nang katulad: nag-i-inject kami ng mock UserService, tumatawag ng loadUser, sinusuri ang estado sa pamamagitan ng XCTestExpectation. Ang Combine Publisher ay sinusuri sa pamamagitan ng XCTestCase na may wait(for: expectations, timeout: 1.0). Ang istraktura ng UserState — enum na may associated values — ay nagpapahintulot sa pagsusuri ng eksaktong estado ng screen pagkatapos ng operasyon.
Saklaw ng code sa mga proyekto ng IT Sectr sa MVVM ay 75-90% para sa ViewModel at Repository. Ang ViewModel ay sakop ng unit-test, ang Repository — ng integration test na may test database. Ang View sa SwiftUI at Jetpack Compose ay sinusuri ng UI test (XCUITest, Compose Test) para sa mga kritikal na sitwasyon. Ang natitirang UI ay sinusuri ng screenshot test (Snapshot Testing) — ito ay mas mabilis kaysa sa UI test at nagbibigay ng 95% kumpiyansa sa kawastuhan ng pagpapakita.
Mga Madalas Itanong
Sa MVVM, ang ViewModel ay walang referensya sa View — ang data ay ipinapadala sa pamamagitan ng mga reaktibong mekanismo (LiveData, StateFlow, @Published). Sa MVP, ang Presenter ay direktang tumatawag ng mga pamamaraan ng View sa pamamagitan ng interface ng ViewContract. Inaalis ng MVVM ang ViewContract at manu-manong attach/detach, ngunit nangangailangan ng pag-unawa sa mga reaktibong daloy. Ang ViewModel ay nakakaligtas sa pag-ikot ng screen sa Android, ang Presenter ay nangangailangan ng retain-fragment.
Minimum na set: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx o kotlinx-coroutines-core (StateFlow). Para sa injection — Hilt o Koin. Para sa asynchronous na operasyon — Kotlin Coroutines. Para sa kumplikadong pagbubuklod ng data — DataBinding. Sa Jetpack Compose (inirerekomenda ng Google mula 2022) ang compose-runtime at lifecycle-viewmodel-compose ay sapat na.
Ang SwiftUI (2019) ay dinisenyo para sa reaktibong arkitektura: ang @State at @Published ay awtomatikong iginuguhit muli ang View kapag nagbago ang data. Ang MVVM — natural na tugma para sa SwiftUI: View — @ViewBuilder, ViewModel — ObservableObject. Hindi ipinipilit ng Apple ang MVVM bilang nag-iisang pattern, ngunit lahat ng materyal sa pagsasanay mula 2019 ay gumagamit ng ViewModel + SwiftUI. Para sa UIKit, inirerekomenda ng Apple ang MVC o Coordinator.
Android: awtomatikong kinakansela ng viewModelScope ang coroutine kapag naglinis ng ViewModel. iOS: ang AnyCancellable mula sa Combine ay awtomatikong nag-unsubscribe kapag pinalaya ang bagay na nag-iimbak nito. Ang SwiftUI @StateObject ay awtomatikong namamahala ng lifecycle. Mga pangunahing patakaran: huwag mag-imbak ng referensya sa View/Context sa ViewModel, kanselahin ang mga matagal na operasyon sa paglilinis, gumamit ng weak self sa mga closure.
StateFlow — modernong pagpili. Ang LiveData ay mas simple at ligtas para sa lifecycle, ngunit ang StateFlow ay mas malakas: gumagana sa coroutine, sumusuporta sa flatMap, combine, filter, hindi nangangailangan ng @Nullable na anotasyon. Ang tanging sitwasyon kung saan mas gusto ang LiveData — pagtatrabaho sa Java code, kung saan ang StateFlow (Kotlin Flow-API) ay hindi available. Inirerekomenda ng Google ang StateFlow para sa mga bagong proyekto sa Kotlin.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din