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) — архитектурен модел, описан от John Gossman през 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 срещу StateFlow — LiveData (2017) — първият реактивен компонент на Jetpack, оптимизиран за жизнения цикъл на Activity: автоматично отписване при onStop. StateFlow (2021) — реализация на Kotlin Flow, независима от жизнения цикъл, но изискваща ръчно отписване чрез 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 срещу @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 (legacy).

Сравнение на 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Чист клас без зависимости
РеактивностCallback-и в PresenterLiveData/StateFlow/Combine

Недостатъци на MVVM — сложност при дебъгване на реактивни вериги и риск от изтичане на памет при неправилен абонамент. LiveData решава проблема с безопасността на жизнения цикъл, 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 и по-ниски). За legacy проекти, където цялата кодова база вече е на 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 е по-прост и безопасен за жизнения цикъл, но 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 срещу MVP — MVVM премахва ViewContract и ръчното attach/detach
  • Тестване — ViewModel покрит с unit-тестове без платформени зависимости
  • Препоръка — MVVM за нови проекти; MVP за поддръжка на legacy

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също