MVVM: what it is, the Model-View-ViewModel pattern in mobile development

Author: IT Sectr Published: 2026-02-16 Reading time: 9 min

MVVM (Model-View-ViewModel) is an architectural pattern in which ViewModel replaces Presenter and uses reactive mechanisms to communicate with View: ObservableObject in SwiftUI, LiveData/StateFlow in Android. ViewModel has no reference to View — data is passed through subscription, which eliminates the need for ViewContract interfaces and makes testing even simpler. Apple has recommended MVVM with SwiftUI since 2019, Google recommends MVVM with Jetpack as the official Android architecture. Learn more in Android Architecture Guide.

Key Takeaways

  • MVVM — Model (data), View (interface), ViewModel (state and logic without reference to View)
  • Reactive binding — LiveData, StateFlow, ObservableObject automatically update UI when data changes
  • ViewModel — survives screen rotation and does not depend on Android SDK/UIKit, testable with unit tests
  • Android Jetpack — ViewModel, LiveData, DataBinding — official stack from Google for MVVM
  • SwiftUI + Combine — native MVVM implementation in iOS with @Published and @ObservedObject

What is MVVM: the essence of the Model-View-ViewModel pattern

MVVM (Model-View-ViewModel) is an architectural pattern described by John Gossman in 2005 for Windows Presentation Foundation (WPF) from Microsoft. ViewModel is the central component that contains screen state and business logic but has no reference to View. Data is passed through reactive binding mechanisms: View subscribes to ViewModel changes and automatically re-renders when data changes.

Key difference between MVVM and MVP — absence of ViewContract. In MVP, Presenter calls view.showUser(data) methods, meaning Presenter actively "pushes" data to View. In MVVM, View itself "pulls" data from ViewModel through subscription: ViewModel does not know whether it has a subscriber. This eliminates the detached View problem — if Activity is destroyed on rotation, ViewModel continues working, and the new Activity simply subscribes to the current data. At IT Sectr, we have been using MVVM in all new projects since 2020 — the code has become more predictable, tests more stable.

ComponentResponsibilityPlatform
ModelData, business logic, repositoriesAndroid/iOS
ViewDisplay, subscription to ViewModelActivity/Composable, UIView/SwiftUI View
ViewModelScreen state, logic, navigationViewModel (Jetpack), ObservableObject

Reactive binding — the foundation of MVVM. In Android, LiveData (part of Jetpack) is an observable data holder. Activity subscribes via observe(): viewModel.user.observe(this) { user -> binding.name.text = user.name }. When user changes, all subscribers receive the new value automatically. On iOS, SwiftUI uses @Published properties in ViewModel — changes automatically re-render View. This eliminates manual showUser/hideLoading calls required in MVP.

MVVM in Android: ViewModel, LiveData and StateFlow

ViewModel from Jetpack — the official component from Google for implementing MVVM. ViewModel survives screen rotation: when configuration changes, Activity is destroyed and recreated, while ViewModel remains in memory. The new Activity instance gets the same ViewModel through ViewModelProvider. ViewModel has no references to Activity, Context or View — it is clean and testable with unit tests without Robolectric.

kotlin
// ViewModel with StateFlow — modern MVVM implementation
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) subscribes to 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 -> /* show loading */
                is UserState.Success -> /* display data */
                is UserState.Error -> /* show error */
            }
        }.launchIn(lifecycleScope)
        viewModel.loadUser(42)
    }
}

LiveData vs StateFlow — LiveData (2017) — the first reactive component of Jetpack, optimized for Activity lifecycle: automatic unsubscription on onStop. StateFlow (2021) — Kotlin Flow implementation, not tied to lifecycle, but requiring manual unsubscription via lifecycleScope. StateFlow supports coroutines, concat, map and other Flow operators, which LiveData lacks. At IT Sectr, we use StateFlow for all new ViewModels — it is shorter, more powerful, and better integrates with coroutines.

DataBinding and ViewBinding — DataBinding binds ViewModel to XML via @{viewModel.user.name} directly in the layout, eliminating code in Activity. ViewBinding generates a type-safe class for accessing views. Google recommends ViewBinding for simple projects and DataBinding for projects with complex data binding. In Jetpack Compose, DataBinding is not needed — @Composable functions automatically re-render when State changes.

MVVM in iOS: ObservableObject and SwiftUI

MVVM in iOS is implemented through ObservableObject from Combine. ViewModel is a class inheriting ObservableObject, with @Published properties. SwiftUI View subscribes to ViewModel via @ObservedObject or @StateObject. When a @Published property changes, SwiftUI automatically re-renders the View that depends on this property. Apple introduced SwiftUI in 2019 at WWDC along with Combine — since then MVVM has become the officially recommended pattern for iOS.

swift
import SwiftUI
import Combine

// ViewModel — ObservableObject with @Published properties
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 — subscribes to 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 creates ViewModel and manages its lifecycle (once per View lifetime). @ObservedObject — ViewModel is created externally and passed to View. WWDC 2022 recommends @StateObject for creation and @ObservedObject for passing ViewModel between Views. In iOS 17 (2023), @Observable macro appeared — automating subscription and eliminating @Published annotations. @Observable is the evolution of Combine, bringing iOS development closer to Kotlin Flow reactivity.

UIKit + MVVM — for UIKit projects (without SwiftUI), MVVM is implemented through Combine and @Published with subscription in UIViewController via sink(). ViewModel is the same, View is UIViewController with subscriptions to @Published. Combine has been available since iOS 13 (2019) and is built into the system — no additional dependencies required. According to Apple Developer Survey (2025), 45% of iOS projects use Combine even with UIKit, 35% use SwiftUI + Combine, 20% use RxSwift (legacy).

MVVM vs MVP: advantages and disadvantages

MVVM wins over MVP in three key aspects: absence of ViewContract interfaces, automatic subscription management, and surviving screen rotation. In MVP, each screen requires a ViewContract interface + Presenter class + subscription/unsubscription in onStart/onStop. In MVVM, only ViewModel is created — subscription in Activity is done via observe() without manual detach().

CriterionMVPMVVM
ViewContract Interfaces1 per screenNot needed
Subscription ManagementManual attach/detachAutomatic (lifecycle-aware)
Screen RotationRetain-fragmentViewModel survives rotation
TestingMock ViewContractClean class without dependencies
ReactivityCallbacks in PresenterLiveData/StateFlow/Combine

Disadvantages of MVVM — complexity of debugging reactive chains and risk of memory leaks with improper subscription. LiveData solves lifecycle safety, StateFlow requires lifecycleScope, Combine requires sink with AnyCancellable. In MVP, all calls are explicit (view.showUser), in MVVM data comes through a reactive stream — tracing requires debug breakpoints in subscribe closures. In large ViewModels with multiple StateFlows, you might miss UI updates if View is not subscribed to a specific Flow.

When MVP is still better — in projects with a minimum Android version below API 21 (Android 5), where Jetpack ViewModel is unavailable without AndroidX, and in projects using pure UIKit without Combine (iOS 12 and below). For legacy projects where the entire codebase is already on MVP, a full transition to MVVM is not always justified — it is cheaper to maintain MVP with gradual extraction of logic into services than to rewrite 100 screens in 3 months.

Testing ViewModel on Android and iOS

ViewModel is tested with unit tests without platform dependencies — this is the main argument in favor of MVVM. On Android, ViewModel does not contain Activity, Context or View — all dependencies (Repository, UseCase) are passed through the constructor and replaced with mock objects. On iOS, ObservableObject is tested through XCTest without launching the application, providing stability and test execution speed.

kotlin
// Android ViewModel unit test with 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 is tested similarly: inject mock UserService, call loadUser, check state through XCTestExpectation. Combine Publisher is tested through XCTestCase with wait(for: expectations, timeout: 1.0). The UserState structure — enum with associated values — allows checking the exact screen state after an operation.

Code coverage in IT Sectr projects using MVVM is 75–90% for ViewModel and Repository. ViewModel is covered by unit tests, Repository by integration tests with a test database. View in SwiftUI and Jetpack Compose is tested with UI tests (XCUITest, Compose Test) for critical scenarios. The rest of the UI is checked with screenshot tests (Snapshot Testing) — this is faster than UI tests and provides 95% confidence in display correctness.

Frequently Asked Questions

What is the main difference between MVVM and MVP?

In MVVM, ViewModel has no reference to View — data is passed through reactive mechanisms (LiveData, StateFlow, @Published). In MVP, Presenter directly calls View methods through the ViewContract interface. MVVM eliminates ViewContract and manual attach/detach, but requires understanding of reactive streams. ViewModel survives screen rotation on Android, Presenter requires a retain-fragment.

What libraries are needed for MVVM on Android?

Minimum set: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx or kotlinx-coroutines-core (StateFlow). For injection — Hilt or Koin. For asynchronous operations — Kotlin Coroutines. For complex data binding — DataBinding. In Jetpack Compose (recommended by Google since 2022), compose-runtime and lifecycle-viewmodel-compose are sufficient.

Why does Apple recommend MVVM for iOS?

SwiftUI (2019) is designed for reactive architecture: @State and @Published automatically re-render View when data changes. MVVM is a natural fit for SwiftUI: View — @ViewBuilder, ViewModel — ObservableObject. Apple does not impose MVVM as the only pattern, but all training materials since 2019 use ViewModel + SwiftUI. For UIKit, Apple recommends MVC or Coordinator.

How to avoid memory leaks in ViewModel?

Android: viewModelScope automatically cancels coroutines when ViewModel is cleared. iOS: AnyCancellable from Combine automatically unsubscribes when the holding object is deallocated. SwiftUI @StateObject manages lifecycle automatically. Main rules: do not store references to View/Context in ViewModel, cancel long-running operations on cleanup, use weak self in closures.

What to choose: LiveData or StateFlow for Android?

StateFlow is the modern choice. LiveData is simpler and lifecycle-safe, but StateFlow is more powerful: works with coroutines, supports flatMap, combine, filter, does not require @Nullable annotation. The only scenario where LiveData is preferable — working with Java code where StateFlow (Kotlin Flow API) is unavailable. Google recommends StateFlow for new Kotlin projects.

Summary

  • MVVM (Model-View-ViewModel) — reactive pattern where ViewModel has no reference to View
  • ViewModel — survives screen rotation, testable with unit tests, independent of UI
  • Android — ViewModel + StateFlow + Kotlin Coroutines — modern stack from Google
  • iOS — ObservableObject + @Published + SwiftUI — native MVVM implementation
  • MVVM vs MVP — MVVM eliminates ViewContract and manual attach/detach
  • Testing — ViewModel is covered by unit tests without platform dependencies
  • Recommendation — MVVM for new projects; MVP for legacy support

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also