MVVM: що це, патерн Model-View-ViewModel в мобільній розробці

Автор: IT Sectr Опубліковано: 2026-02-16 Час читання: 9 хв

MVVM (Model-View-ViewModel) — архітектурний патерн, у якому ViewModel замінює Presenter і використовує реактивні механізми для зв'язку з View: ObservableObject у SwiftUI, LiveData/StateFlow в Android. ViewModel не має посилання на View — дані передаються через підписку, що усуває необхідність в інтерфейсах ViewContract і робить тестування ще простішим. Apple рекомендує MVVM з SwiftUI з 2019 року, Google — MVVM з Jetpack як офіційну архітектуру Android. Детальніше — у Android Architecture Guide.

Головне

  • MVVM — Model (дані), View (інтерфейс), ViewModel (стан і логіка без посилання на View)
  • Reactive binding — LiveData, StateFlow, ObservableObject автоматично оновлюють UI при зміні даних
  • ViewModel — переживає поворот екрана і не залежить від Android SDK/UIKit, тестується unit-тестами
  • Android Jetpack — ViewModel, LiveData, DataBinding — офіційний стек від Google для MVVM
  • SwiftUI + Combine — нативна реалізація MVVM в iOS з @Published і @ObservedObject

Що таке MVVM: суть патерну Model-View-ViewModel

MVVM (Model-View-ViewModel) — архітектурний патерн, описаний Джоном Госсманом у 2005 році для Windows Presentation Foundation (WPF) від Microsoft. ViewModel — центральний компонент, який містить стан екрана та бізнес-логіку, але не має посилання на View. Дані передаються через механізми реактивного зв'язування: View підписується на зміни ViewModel і автоматично перемальовується, коли дані змінюються.

Ключова відмінність MVVM від MVP — відсутність ViewContract. У MVP Presenter викликає методи view.showUser(data), тобто Presenter активно «штовхає» дані у View. У MVVM View сама «тягне» дані з ViewModel через підписку: ViewModel не знає, чи є в неї підписник. Це усуває проблему від'єднаного View — якщо Activity знищена при повороті, ViewModel продовжує роботу, а нова Activity просто підписується на актуальні дані. В IT Sectr ми використовуємо MVVM у всіх нових проєктах з 2020 року — код став передбачуванішим, тести стабільнішими.

КомпонентВідповідальністьПлатформа
ModelДані, бізнес-логіка, репозиторіїAndroid/iOS
ViewВідображення, підписка на ViewModelActivity/Composable, UIView/SwiftUI View
ViewModelСтан екрана, логіка, навігаціяViewModel (Jetpack), ObservableObject

Реактивне зв'язування — фундамент MVVM. В Android LiveData (частина Jetpack) є спостережуваним сховищем даних. Activity підписується через observe(): viewModel.user.observe(this) { user -> binding.name.text = user.name }. При зміні user всі підписники отримують нове значення автоматично. На iOS SwiftUI використовує @Published-властивості в ViewModel — зміни автоматично перемальовують View. Це усуває ручні виклики showUser/hideLoading, які потрібні в MVP.

MVVM в Android: ViewModel, LiveData і StateFlow

ViewModel з Jetpack — офіційний компонент від Google для реалізації MVVM. ViewModel переживає поворот екрана: при зміні конфігурації Activity знищується та перестворюється, а ViewModel залишається в пам'яті. Новий екземпляр Activity отримує ту саму ViewModel через ViewModelProvider. ViewModel не має посилань на Activity, Context або View — вона чиста і тестується unit-тестами без Robolectric.

kotlin
// ViewModel з StateFlow — сучасна реалізація 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) підписується на 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 -> /* показать загрузку */
                is UserState.Success -> /* отобразить данные */
                is UserState.Error -> /* показать ошибку */
            }
        }.launchIn(lifecycleScope)
        viewModel.loadUser(42)
    }
}

LiveData vs StateFlow — LiveData (2017) — перший реактивний компонент Jetpack, оптимізований для Activity lifecycle: автоматична відписка при onStop. StateFlow (2021) — реалізація Kotlin Flow, не прив'язана до lifecycle, але потребує ручної відписки через lifecycleScope. StateFlow підтримує coroutines, concat, map та інші оператори Flow, яких немає в LiveData. В IT Sectr ми використовуємо StateFlow для всіх нових ViewModel — він коротший, потужніший і краще інтегрується з корутинами.

DataBinding і ViewBinding — DataBinding зв'язує ViewModel з XML через @{viewModel.user.name} прямо в розмітці, усуваючи код в Activity. ViewBinding генерує типобезпечний клас для доступу до View. Google рекомендує ViewBinding для простих проєктів і DataBinding для проєктів зі складним зв'язуванням даних. В Jetpack Compose DataBinding не потрібен — @Composable функції автоматично перемальовуються при зміні State.

MVVM в iOS: ObservableObject і SwiftUI

MVVM в iOS реалізується через ObservableObject з Combine. ViewModel — клас, що успадковує ObservableObject, з полями @Published. SwiftUI View підписується на ViewModel через @ObservedObject або @StateObject. При зміні @Published-властивості SwiftUI автоматично перемальовує View, яка залежить від цієї властивості. Apple представила SwiftUI в 2019 році на WWDC разом з Combine — з цього моменту MVVM став офіційно рекомендованим патерном для iOS.

swift
import SwiftUI
import Combine

// ViewModel — ObservableObject з @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 — підписується на 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 створює ViewModel і керує її життєвим циклом (один раз за час життя View). @ObservedObject — ViewModel створюється ззовні і передається в View. WWDC 2022 рекомендує @StateObject для створення і @ObservedObject для передачі ViewModel між Views. В iOS 17 (2023) з'явився @Observable — макрос, що автоматизує підписку та усуває @Published-аннотації. @Observable — еволюція Combine, що наближає iOS-розробку до реактивності Kotlin Flow.

UIKit + MVVM — для проєктів на UIKit (без SwiftUI) MVVM реалізується через Combine і @Published з підпискою в UIViewController через sink(). ViewModel та сама, View — UIViewController з підписками на @Published. Combine доступний з iOS 13 (2019) і вбудований в систему — не потребує додаткових залежностей. За даними Apple Developer Survey (2025), 45% iOS-проєктів використовують Combine навіть при UIKit, 35% використовують SwiftUI + Combine, 20% — RxSwift (легасі).

Порівняння MVVM з MVP: переваги та недоліки

MVVM виграє у MVP в трьох ключових аспектах: відсутність інтерфейсів ViewContract, автоматичне керування підписками та переживання повороту екрана. В MVP кожен екран потребує інтерфейс ViewContract + клас Presenter + підписку/відписку в onStart/onStop. В MVVM створюється тільки ViewModel — підписка в Activity виконується через observe() без ручного detach().

КритерійMVPMVVM
Інтерфейси ViewContract1 на екранНе потрібні
Керування підпискамиРучне attach/detachАвтоматичне (lifecycle-aware)
Поворот екранаRetain-фрагментViewModel переживає поворот
ТестуванняMock ViewContractЧистий клас без залежностей
РеактивністьКолбеки в PresenterLiveData/StateFlow/Combine

Недоліки MVVM — складність налагодження реактивних ланцюжків і ризик витоку пам'яті при неправильній підписці. LiveData вирішує проблему lifecycle-безпеки, StateFlow потребує lifecycleScope, Combine — sink з AnyCancellable. В MVP всі виклики явні (view.showUser), в MVVM дані надходять через реактивний потік — трасування вимагає debug-брейкпоінтів у subscribe-замиканні. У великих ViewModel з безліччю StateFlow можна пропустити оновлення UI, якщо View не підписана на конкретний Flow.

Коли MVP все ще кращий — в проєктах з мінімальною версією Android нижче API 21 (Android 5), де Jetpack ViewModel недоступний без AndroidX, і в проєктах на чистому UIKit без Combine (iOS 12 і нижче). Для легасі-проєктів, де вся кодова база вже на MVP, повний перехід на MVVM не завжди виправданий — дешевше підтримувати MVP з поступовим винесенням логіки в сервіси, ніж переписувати 100 екранів за 3 місяці.

Тестування ViewModel на Android та iOS

ViewModel тестується unit-тестами без платформних залежностей — це головний аргумент на користь MVVM. На Android ViewModel не містить Activity, Context або View — всі залежності (Repository, UseCase) передаються через конструктор і замінюються mock-об'єктами. На iOS ObservableObject тестується через XCTest без запуску додатка, що дає стабільність і швидкість виконання тестів.

kotlin
// Unit-тест Android ViewModel з 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 тестується аналогічно: інжектимо mock UserService, викликаємо loadUser, перевіряємо стан через XCTestExpectation. Combine Publisher тестується через XCTestCase з wait(for: expectations, timeout: 1.0). Структура UserState — enum з associated values — дозволяє перевірити точний стан екрана після операції.

Покриття коду в проєктах IT Sectr на MVVM становить 75–90% для ViewModel і Repository. ViewModel покривається unit-тестами, Repository — інтеграційними тестами з тестовою БД. View в SwiftUI і Jetpack Compose тестується UI-тестами (XCUITest, Compose Test) для критичних сценаріїв. Решта UI перевіряється скріншотними тестами (Snapshot Testing) — це швидше, ніж UI-тести, і дає 95% впевненості в коректності відображення.

Часті запитання

В чому основна відмінність MVVM від MVP?

В MVVM ViewModel не має посилання на View — дані передаються через реактивні механізми (LiveData, StateFlow, @Published). В MVP Presenter напряму викликає методи View через інтерфейс ViewContract. MVVM усуває ViewContract і ручне attach/detach, але потребує розуміння реактивних потоків. ViewModel переживає поворот екрана на Android, Presenter потребує retain-фрагмент.

Які бібліотеки потрібні для MVVM на Android?

Мінімальний набір: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx або kotlinx-coroutines-core (StateFlow). Для інжекції — Hilt або Koin. Для асинхронних операцій — Kotlin Coroutines. Для складного зв'язування даних — DataBinding. В Jetpack Compose (рекомендованому Google з 2022) достатньо compose-runtime і lifecycle-viewmodel-compose.

Чому Apple рекомендує MVVM для iOS?

SwiftUI (2019) спроєктований під реактивну архітектуру: @State і @Published автоматично перемальовують View при зміні даних. MVVM — природна відповідність SwiftUI: View — @ViewBuilder, ViewModel — ObservableObject. Apple не нав'язує MVVM як єдиний патерн, але всі навчальні матеріали з 2019 року використовують ViewModel + SwiftUI. Для UIKit Apple рекомендує MVC або Coordinator.

Як уникнути витоків пам'яті в ViewModel?

Android: viewModelScope автоматично скасовує корутини при очищенні ViewModel. iOS: AnyCancellable з Combine автоматично відписується при звільненні об'єкта, що його зберігає. SwiftUI @StateObject керує життєвим циклом автоматично. Основні правила: не зберігати посилання на View/Context в ViewModel, скасовувати довготривалі операції при очищенні, використовувати weak self в замиканнях.

Що обрати: LiveData чи StateFlow для Android?

StateFlow — сучасний вибір. LiveData простіший і lifecycle-безпечний, але StateFlow потужніший: працює з корутинами, підтримує flatMap, combine, filter, не потребує анотації @Nullable. Єдиний сценарій, де LiveData кращий — робота з Java-кодом, де StateFlow (Kotlin Flow-API) недоступний. Google рекомендує StateFlow для нових проєктів на Kotlin.

Підсумки

  • MVVM (Model-View-ViewModel) — реактивний патерн, де ViewModel не має посилання на View
  • ViewModel — переживає поворот екрана, тестується unit-тестами, незалежна від UI
  • Android — ViewModel + StateFlow + Kotlin Coroutines — сучасний стек від Google
  • iOS — ObservableObject + @Published + SwiftUI — нативна реалізація MVVM
  • MVVM vs MVP — MVVM усуває ViewContract і ручне attach/detach
  • Тестування — ViewModel покривається unit-тестами без платформних залежностей
  • Рекомендація — MVVM для нових проєктів; MVP для підтримки легасі

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також