MVVM: mi ez, a Model-View-ViewModel minta a mobilfejlesztésben

Szerző: IT Sectr Megjelenés: 2026-02-16 Olvasási idő: 9 perc

MVVM (Model-View-ViewModel) — egy architekturális minta, amelyben a ViewModel helyettesíti a Presentert és reaktív mechanizmusokat használ a View-val való kommunikációhoz: ObservableObject a SwiftUI-ban, LiveData/StateFlow Androidban. A ViewModel nem rendelkezik referenciával a View-ra — az adatok előfizetésen keresztül kerülnek továbbításra, ami kiküszöböli a ViewContract interfészek szükségességét és még egyszerűbbé teszi a tesztelést. Az Apple 2019 óta ajánlja az MVVM-et SwiftUI-val, a Google — az MVVM-et Jetpackkel mint az Android hivatalos architektúráját. Bővebben a Android Architecture Guide-ban.

Főbb pontok

  • MVVM — Model (adatok), View (felület), ViewModel (állapot és logika View referenciája nélkül)
  • Reactive binding — LiveData, StateFlow, ObservableObject automatikusan frissíti a UI-t az adatok változásakor
  • ViewModel — túléli a képernyő elforgatását és nem függ az Android SDK/UIKit-től, unittesztekkel tesztelhető
  • Android Jetpack — ViewModel, LiveData, DataBinding — a Google hivatalos stack-je az MVVM-hez
  • SwiftUI + Combine — az MVVM natív megvalósítása iOS-ben @Published és @ObservedObject segítségével

Mi az MVVM: a Model-View-ViewModel minta lényege

MVVM (Model-View-ViewModel) — egy architekturális minta, amelyet John Gossman írt le 2005-ben a Microsoft Windows Presentation Foundation (WPF) rendszeréhez. A ViewModel — a központi komponens, amely tartalmazza a képernyő állapotát és az üzleti logikát, de nem rendelkezik referenciával a View-ra. Az adatok reaktív kötési mechanizmusokon keresztül kerülnek továbbításra: a View feliratkozik a ViewModel változásaira, és automatikusan újrarajzolódik, amikor az adatok megváltoznak.

Az MVVM legfontosabb különbsége az MVP-től — a ViewContract hiánya. Az MVP-ben a Presenter meghívja a view.showUser(data) metódusokat, vagyis a Presenter aktívan “tolja” az adatokat a View-ba. Az MVVM-ben a View maga “húzza” az adatokat a ViewModel-ből előfizetésen keresztül: a ViewModel nem tudja, van-e előfizetője. Ez kiküszöböli a leválasztott View problémáját — ha az Activity megsemmisül elforgatáskor, a ViewModel folytatja a munkát, és az új Activity egyszerűen feliratkozik az aktuális adatokra. Az IT Sectr-ben 2020 óta minden új projektben MVVM-et használunk — a kód kiszámíthatóbb lett, a tesztek stabilabbak.

KomponensFelelősségPlatform
ModelAdatok, üzleti logika, repozitóriumokAndroid/iOS
ViewMegjelenítés, feliratkozás ViewModel-reActivity/Composable, UIView/SwiftUI View
ViewModelKépernyő állapota, logika, navigációViewModel (Jetpack), ObservableObject

Reaktív kötés — az MVVM alapja. Androidban a LiveData (a Jetpack része) egy megfigyelhető adattároló. Az Activity az observe()-en keresztül iratkozik fel: viewModel.user.observe(this) { user -> binding.name.text = user.name }. Amikor a user megváltozik, minden előfizető automatikusan megkapja az új értéket. iOS-ben a SwiftUI @Published tulajdonságokat használ a ViewModel-ben — a változások automatikusan újrarajzolják a View-t. Ez kiküszöböli a kézi showUser/hideLoading hívásokat, amelyek az MVP-ben szükségesek.

MVVM Androidban: ViewModel, LiveData és StateFlow

ViewModel a Jetpackből — a Google hivatalos komponense az MVVM megvalósításához. A ViewModel túléli a képernyő elforgatását: konfigurációváltáskor az Activity megsemmisül és újra létrejön, míg a ViewModel a memóriában marad. Az Activity új példánya ugyanazt a ViewModel-t kapja a ViewModelProvider-en keresztül. A ViewModel nem rendelkezik referenciával az Activity-re, Context-re vagy View-ra — tiszta, és Robolectric nélkül unittesztekkel tesztelhető.

kotlin
// ViewModel StateFlow-val — az MVVM modern megvalósítása
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) feliratkozik a state-re
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 -> /* betöltés megjelenítése */
                is UserState.Success -> /* adatok megjelenítése */
                is UserState.Error -> /* hiba megjelenítése */
            }
        }.launchIn(lifecycleScope)
        viewModel.loadUser(42)
    }
}

LiveData vs StateFlow — a LiveData (2017) — az első reaktív Jetpack komponens, optimalizálva az Activity életciklusára: automatikus leiratkozás onStop-kor. A StateFlow (2021) — Kotlin Flow megvalósítás, nem kötődik az életciklushoz, de kézi leiratkozást igényel a lifecycleScope-on keresztül. A StateFlow támogatja a coroutine-okat, concat, map és más Flow operátorokat, amelyek a LiveData-ban nincsenek. Az IT Sectr-ben a StateFlow-t használjuk minden új ViewModel-hez — rövidebb, erősebb és jobban integrálódik a korutinokkal.

DataBinding és ViewBinding — a DataBinding a ViewModel-t XML-hez köti a @{viewModel.user.name} segítségével közvetlenül a layout-ban, kiküszöbölve a kódot az Activity-ből. A ViewBinding egy típusbiztos osztályt generál a View eléréséhez. A Google a ViewBinding-et ajánlja egyszerű projektekhez, a DataBinding-et pedig összetett adatkötésű projektekhez. A Jetpack Compose-ban nincs szükség DataBinding-re — a @Composable függvények automatikusan újrarajzolódnak a State változásakor.

MVVM iOS-ben: ObservableObject és SwiftUI

MVVM iOS-ben a Combine-ból származó ObservableObject segítségével valósul meg. A ViewModel — egy osztály, amely az ObservableObject-ből örököl, @Published mezőkkel. A SwiftUI View a @ObservedObject vagy @StateObject segítségével iratkozik fel a ViewModel-re. Amikor egy @Published tulajdonság megváltozik, a SwiftUI automatikusan újrarajzolja a View-t, amely ettől a tulajdonságtól függ. Az Apple 2019-ben mutatta be a SwiftUI-t a WWDC-n a Combine-nal együtt — ettől a pillanattól kezdve az MVVM hivatalosan ajánlott mintává vált iOS-ben.

swift
import SwiftUI
import Combine

// ViewModel — ObservableObject @Published mezőkkel
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 — feliratkozik a ViewModel-re
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 — a @StateObject létrehozza a ViewModel-t és kezeli annak életciklusát (egyszer a View élettartama alatt). A @ObservedObject — a ViewModel kívülről jön létre, és átadásra kerül a View-nak. A WWDC 2022 a @StateObject-t ajánlja létrehozáshoz, a @ObservedObject-t pedig a ViewModel View-k közötti átadásához. Az iOS 17-ben (2023) megjelent a @Observable — egy makró, amely automatizálja az előfizetést és kiküszöböli a @Published annotációkat. A @Observable — a Combine evolúciója, amely közelebb hozza az iOS-fejlesztést a Kotlin Flow reaktivitásához.

UIKit + MVVM — UIKit-es projektekhez (SwiftUI nélkül) az MVVM a Combine és @Published segítségével valósul meg, előfizetéssel az UIViewController-ben a sink()-en keresztül. A ViewModel ugyanaz, a View — UIViewController @Published előfizetésekkel. A Combine iOS 13-tól (2019) érhető el, és beépül a rendszerbe — nem igényel további függőségeket. Az Apple Developer Survey (2025) szerint az iOS-projektek 45%-a használ Combine-t még UIKit esetén is, 35% használ SwiftUI + Combine-t, 20% — RxSwift (legacy).

Az MVVM összehasonlítása az MVP-vel: előnyök és hátrányok

Az MVVM felülmúlja az MVP-t három kulcsfontosságú szempontból: a ViewContract interfészek hiánya, az előfizetések automatikus kezelése és a képernyőelforgatás túlélése. Az MVP-ben minden képernyőhöz szükség van egy ViewContract interfészre + Presenter osztályra + előfizetés/leiratkozás az onStart/onStop-ban. Az MVVM-ben csak a ViewModel jön létre — az Activity-ben az előfizetés az observe()-en keresztül történik kézi detach() nélkül.

SzempontMVPMVVM
ViewContract interfészek1 képernyőnkéntNem szükségesek
Előfizetések kezeléseKézi attach/detachAutomatikus (lifecycle-aware)
Képernyő elforgatásaRetain-fragmentViewModel túléli
TesztelésMock ViewContractTiszta osztály függőségek nélkül
ReaktivitásCallback-ek a Presenter-benLiveData/StateFlow/Combine

Az MVVM hátrányai — a reaktív láncok hibakeresésének bonyolultsága és a memóriaszivárgás kockázata helytelen előfizetés esetén. A LiveData megoldja az életciklus-biztonság problémáját, a StateFlow lifecycleScope-ot igényel, a Combine — sink-et AnyCancellable-lel. Az MVP-ben minden hívás explicit (view.showUser), az MVVM-ben az adatok reaktív folyamon keresztül érkeznek — a nyomkövetés debug-töréspontokat igényel a subscribe lezárásban. Nagy ViewModel-ekben sok StateFlow-val előfordulhat, hogy a UI-frissítés elmarad, ha a View nincs feliratkozva egy adott Flow-ra.

Mikor jobb még az MVP — olyan projektekben, ahol a minimális Android verzió API 21 (Android 5) alatt van, ahol a Jetpack ViewModel AndroidX nélkül nem érhető el, és tiszta UIKit-es projektekben Combine nélkül (iOS 12 és alatta). Olyan legacy projekteknél, ahol a teljes kódbázis már MVP-n alapul, a teljes átállás az MVVM-re nem mindig indokolt — olcsóbb fenntartani az MVP-t a logika fokozatos szolgáltatásokba helyezésével, mint 100 képernyőt átírni 3 hónap alatt.

ViewModel tesztelése Androidon és iOS-ben

A ViewModel unittesztekkel tesztelhető platformfüggőségek nélkül — ez a fő érv az MVVM mellett. Androidon a ViewModel nem tartalmaz Activity-t, Context-et vagy View-t — minden függőség (Repository, UseCase) a konstruktoron keresztül kerül átadásra, és mock objektumokkal helyettesíthető. iOS-ben az ObservableObject XCTesten keresztül tesztelhető az alkalmazás elindítása nélkül, ami stabilitást és gyorsaságot biztosít a tesztek végrehajtásában.

kotlin
// Android ViewModel unit teszt MockK-val
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 hasonlóan tesztelhető: injektálunk egy mock UserService-t, meghívjuk a loadUser-t, ellenőrizzük az állapotot XCTestExpectation segítségével. A Combine Publisher XCTestCase-en keresztül tesztelhető wait(for: expectations, timeout: 1.0) paraméterrel. A UserState struktúra — enum associated values-szel — lehetővé teszi a képernyő pontos állapotának ellenőrzését a művelet után.

Kódlefedettség az IT Sectr MVVM-es projektjeiben 75-90% a ViewModel és Repository esetében. A ViewModel-t unittesztek fedik le, a Repository-t — integrációs tesztek tesztadatbázissal. A View SwiftUI-ban és Jetpack Compose-ban UI-tesztekkel (XCUITest, Compose Test) tesztelhető kritikus forgatókönyvek esetén. A UI többi része képernyőkép-tesztekkel (Snapshot Testing) ellenőrizhető — ez gyorsabb, mint a UI-tesztek, és 95%-os biztonságot nyújt a megjelenítés helyességében.

Gyakran Ismételt Kérdések

Mi a fő különbség az MVVM és az MVP között?

Az MVVM-ben a ViewModel nem rendelkezik referenciával a View-ra — az adatok reaktív mechanizmusokon (LiveData, StateFlow, @Published) keresztül kerülnek továbbításra. Az MVP-ben a Presenter közvetlenül hívja meg a View metódusait a ViewContract interfészen keresztül. Az MVVM kiküszöböli a ViewContract-ot és a kézi attach/detach-t, de megköveteli a reaktív folyamatok megértését. A ViewModel Androidon túléli a képernyő elforgatását, a Presenter retain-fragment-et igényel.

Milyen könyvtárak szükségesek az MVVM-hez Androidon?

Minimális készlet: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx vagy kotlinx-coroutines-core (StateFlow). Injektáláshoz — Hilt vagy Koin. Aszinkron műveletekhez — Kotlin Coroutines. Összetett adatkötéshez — DataBinding. Jetpack Compose-ban (amelyet a Google 2022 óta ajánl) elegendő a compose-runtime és a lifecycle-viewmodel-compose.

Miért ajánlja az Apple az MVVM-et iOS-hez?

A SwiftUI (2019) reaktív architektúrára lett tervezve: a @State és @Published automatikusan újrarajzolja a View-t az adatok változásakor. Az MVVM természetes illeszkedés a SwiftUI-hoz: View — @ViewBuilder, ViewModel — ObservableObject. Az Apple nem erőlteti az MVVM-et egyetlen mintaként, de minden oktatási anyag 2019 óta ViewModel + SwiftUI-t használ. A UIKit-hez az Apple az MVC-t vagy a Coordinator-t ajánlja.

Hogyan kerüljük el a memóriaszivárgást a ViewModel-ben?

Android: a viewModelScope automatikusan lemondja a korutinokat a ViewModel tisztításakor. iOS: a Combine-ból származó AnyCancellable automatikusan leiratkozik a tároló objektum felszabadításakor. A SwiftUI @StateObject automatikusan kezeli az életciklust. Fő szabályok: ne tároljon referenciákat View/Context-re a ViewModel-ben, mondja le a hosszú futású műveleteket tisztításkor, használjon weak self-et a lezárásokban.

Mit válasszunk: LiveData-t vagy StateFlow-t Androidhoz?

A StateFlow — a modern választás. A LiveData egyszerűbb és életciklus-biztos, de a StateFlow erősebb: korutinokkal működik, támogatja a flatMap, combine, filter funkciókat, nem igényel @Nullable annotációt. Az egyetlen forgatókönyv, ahol a LiveData előnyösebb — Java kóddal való munka, ahol a StateFlow (Kotlin Flow-API) nem érhető el. A Google a StateFlow-t ajánlja új Kotlin projektekhez.

Összefoglalás

  • MVVM (Model-View-ViewModel) — reaktív minta, ahol a ViewModel nem rendelkezik referenciával a View-ra
  • ViewModel — túléli a képernyő elforgatását, unittesztekkel tesztelhető, UI-tól független
  • Android — ViewModel + StateFlow + Kotlin Coroutines — modern stack a Google-tól
  • iOS — ObservableObject + @Published + SwiftUI — az MVVM natív megvalósítása
  • MVVM vs MVP — az MVVM kiküszöböli a ViewContract-ot és a kézi attach/detach-t
  • Tesztelés — a ViewModel unittesztekkel lefedhető platformfüggőségek nélkül
  • Ajánlás — MVVM új projektekhez; MVP legacy támogatáshoz

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is