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 vs 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 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 (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Чиста класа без зависности
РеактивностПовратни позиви у Presenter-уLiveData/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 vs MVP — MVVM елиминише ViewContract и ручно attach/detach
  • Тестирање — ViewModel покривен unit-тестовима без платформских зависности
  • Препорука — MVVM за нове пројекте; MVP за одржавање legacy-ја

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође