MVVM (Model-View-ViewModel) — architektonický vzor, ve kterém ViewModel nahrazuje Presenter a používá reaktivní mechanismy pro komunikaci s View: ObservableObject ve SwiftUI, LiveData/StateFlow v Android. ViewModel nemá odkaz na View — data jsou přenášena prostřednictvím předplatného, což eliminuje potřebu rozhraní ViewContract a činí testování ještě jednodušším. Apple doporučuje MVVM se SwiftUI od roku 2019, Google — MVVM s Jetpack jako oficiální architekturu Android. Více v Android Architecture Guide.
Hlavní body
MVVM (Model-View-ViewModel) — architektonický vzor popsaný Johnem Gossmanem v roce 2005 pro Windows Presentation Foundation (WPF) od Microsoft. ViewModel — centrální komponenta, která obsahuje stav obrazovky a obchodní logiku, ale nemá odkaz na View. Data jsou přenášena prostřednictvím mechanismů reaktivního vázání: View se přihlásí k odběru změn ViewModel a automaticky se překreslí, když se data změní.
Klíčový rozdíl MVVM od MVP — absence ViewContract. V MVP Presenter volá metody view.showUser(data), to znamená, že Presenter aktivně “tlačí” data do View. V MVVM View sama “táhne” data z ViewModel prostřednictvím předplatného: ViewModel neví, zda má odběratele. To eliminuje problém odpojeného View — pokud je Activity zničeno při otočení, ViewModel pokračuje v práci a nové Activity se jednoduše přihlásí k aktuálním datům. V IT Sectr používáme MVVM ve všech nových projektech od roku 2020 — kód se stal předvídatelnějším, testy stabilnějšími.
| Komponenta | Odpovědnost | Platforma |
|---|---|---|
| Model | Data, obchodní logika, repozitáře | Android/iOS |
| View | Zobrazení, odběr ViewModel | Activity/Composable, UIView/SwiftUI View |
| ViewModel | Stav obrazovky, logika, navigace | ViewModel (Jetpack), ObservableObject |
Reaktivní vázání — základ MVVM. V Android je LiveData (součást Jetpack) pozorovatelným úložištěm dat. Activity se přihlásí k odběru prostřednictvím observe(): viewModel.user.observe(this) { user -> binding.name.text = user.name }. Při změně user všichni odběratelé automaticky obdrží novou hodnotu. V iOS SwiftUI používá @Published vlastnosti ve ViewModel — změny automaticky překreslí View. To eliminuje ruční volání showUser/hideLoading, která jsou vyžadována v MVP.
ViewModel z Jetpack — oficiální komponenta od Google pro implementaci MVVM. ViewModel přežije otočení obrazovky: při změně konfigurace je Activity zničeno a znovu vytvořeno, zatímco ViewModel zůstává v paměti. Nová instance Activity obdrží stejný ViewModel prostřednictvím ViewModelProvider. ViewModel nemá odkazy na Activity, Context nebo View — je čistý a testuje se unit testy bez Robolectric.
// ViewModel se StateFlow — moderní implementace 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) se přihlásí k odběru 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 -> /* zobrazit načítání */
is UserState.Success -> /* zobrazit data */
is UserState.Error -> /* zobrazit chybu */
}
}.launchIn(lifecycleScope)
viewModel.loadUser(42)
}
}
LiveData vs StateFlow — LiveData (2017) — první reaktivní komponenta Jetpack, optimalizovaná pro životní cyklus Activity: automatické odhlášení při onStop. StateFlow (2021) — implementace Kotlin Flow, nezávislá na životním cyklu, ale vyžadující ruční odhlášení prostřednictvím lifecycleScope. StateFlow podporuje coroutines, concat, map a další operátory Flow, které LiveData nemá. V IT Sectr používáme StateFlow pro všechna nová ViewModel — je kratší, výkonnější a lépe se integruje s korutinami.
DataBinding a ViewBinding — DataBinding propojuje ViewModel s XML pomocí @{viewModel.user.name} přímo v rozvržení, čímž eliminuje kód v Activity. ViewBinding generuje typově bezpečnou třídu pro přístup k View. Google doporučuje ViewBinding pro jednoduché projekty a DataBinding pro projekty se složitým vázáním dat. V Jetpack Compose není DataBinding potřeba — funkce @Composable se automaticky překreslí při změně State.
MVVM v iOS je implementováno prostřednictvím ObservableObject z Combine. ViewModel — třída dědící ObservableObject s @Published poli. SwiftUI View se přihlásí k odběru ViewModel prostřednictvím @ObservedObject nebo @StateObject. Při změně @Published vlastnosti SwiftUI automaticky překreslí View, které na této vlastnosti závisí. Apple představil SwiftUI v roce 2019 na WWDC spolu s Combine — od té chvíle se MVVM stal oficiálně doporučeným vzorem pro iOS.
import SwiftUI
import Combine
// ViewModel — ObservableObject s @Published poli
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 — přihlásí se k odběru 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 vytváří ViewModel a spravuje jeho životní cyklus (jednou za dobu života View). @ObservedObject — ViewModel je vytvořen externě a předán do View. WWDC 2022 doporučuje @StateObject pro vytváření a @ObservedObject pro předávání ViewModel mezi Views. V iOS 17 (2023) se objevil @Observable — makro, které automatizuje odběr a eliminuje @Published anotace. @Observable — evoluce Combine, přibližující iOS vývoj reaktivitě Kotlin Flow.
UIKit + MVVM — pro projekty na UIKit (bez SwiftUI) je MVVM implementováno prostřednictvím Combine a @Published s odběrem v UIViewController pomocí sink(). ViewModel je stejný, View — UIViewController s odběry na @Published. Combine je dostupný od iOS 13 (2019) a je integrován v systému — nevyžaduje další závislosti. Podle Apple Developer Survey (2025) 45% iOS projektů používá Combine i při UIKit, 35% používá SwiftUI + Combine, 20% — RxSwift (legacy).
MVVM vítězí nad MVP ve třech klíčových aspektech: absence rozhraní ViewContract, automatická správa odběrů a přežití otočení obrazovky. V MVP každá obrazovka vyžaduje rozhraní ViewContract + třídu Presenter + odběr/odhlášení v onStart/onStop. V MVVM se vytváří pouze ViewModel — odběr v Activity se provádí prostřednictvím observe() bez ručního detach().
| Kritérium | MVP | MVVM |
|---|---|---|
| Rozhraní ViewContract | 1 na obrazovku | Nejsou potřeba |
| Správa odběrů | Ruční attach/detach | Automatická (lifecycle-aware) |
| Otočení obrazovky | Retain-fragment | ViewModel přežije |
| Testování | Mock ViewContract | Čistá třída bez závislostí |
| Reaktivita | Callbacky v Presenter | LiveData/StateFlow/Combine |
Nevýhody MVVM — složitost ladění reaktivních řetězců a riziko úniku paměti při nesprávném odběru. LiveData řeší problém bezpečnosti životního cyklu, StateFlow vyžaduje lifecycleScope, Combine — sink s AnyCancellable. V MVP jsou všechna volání explicitní (view.showUser), v MVVM data přicházejí reaktivním proudem — sledování vyžaduje debug breakpointy v subscribe uzávěře. Ve velkých ViewModel s mnoha StateFlow lze zmeškat aktualizaci UI, pokud View není přihlášeno k odběru konkrétního Flow.
Kdy je MVP stále lepší — v projektech s minimální verzí Android pod API 21 (Android 5), kde Jetpack ViewModel není dostupný bez AndroidX, a v projektech na čistém UIKit bez Combine (iOS 12 a níže). Pro legacy projekty, kde je celá kódová báze již na MVP, není úplný přechod na MVVM vždy opodstatněný — levnější je udržovat MVP s postupným přesunem logiky do služeb, než přepisovat 100 obrazovek za 3 měsíce.
ViewModel je testován unit testy bez platformních závislostí — to je hlavní argument ve prospěch MVVM. Na Android ViewModel neobsahuje Activity, Context ani View — všechny závislosti (Repository, UseCase) jsou předávány konstruktorem a nahrazovány mock objekty. Na iOS je ObservableObject testován prostřednictvím XCTest bez spouštění aplikace, což poskytuje stabilitu a rychlost provádění testů.
// Unit test Android ViewModel s 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 je testován obdobně: injektujeme mock UserService, zavoláme loadUser, zkontrolujeme stav prostřednictvím XCTestExpectation. Combine Publisher je testován prostřednictvím XCTestCase s wait(for: expectations, timeout: 1.0). Struktura UserState — enum s associated values — umožňuje zkontrolovat přesný stav obrazovky po operaci.
Pokrytí kódu v projektech IT Sectr na MVVM je 75-90% pro ViewModel a Repository. ViewModel je pokryt unit testy, Repository — integračními testy s testovací databází. View ve SwiftUI a Jetpack Compose je testováno UI testy (XCUITest, Compose Test) pro kritické scénáře. Zbývající UI je kontrolováno screenshot testy (Snapshot Testing) — to je rychlejší než UI testy a poskytuje 95% jistotu ve správnost zobrazení.
Často kladené otázky
V MVVM ViewModel nemá odkaz na View — data jsou přenášena prostřednictvím reaktivních mechanismů (LiveData, StateFlow, @Published). V MVP Presenter přímo volá metody View prostřednictvím rozhraní ViewContract. MVVM eliminuje ViewContract a ruční attach/detach, ale vyžaduje pochopení reaktivních toků. ViewModel přežije otočení obrazovky na Android, Presenter vyžaduje retain-fragment.
Minimální sada: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx nebo kotlinx-coroutines-core (StateFlow). Pro injekci — Hilt nebo Koin. Pro asynchronní operace — Kotlin Coroutines. Pro složité vázání dat — DataBinding. V Jetpack Compose (doporučeném Google od 2022) stačí compose-runtime a lifecycle-viewmodel-compose.
SwiftUI (2019) byl navržen pro reaktivní architekturu: @State a @Published automaticky překreslí View při změně dat. MVVM — přirozená shoda se SwiftUI: View — @ViewBuilder, ViewModel — ObservableObject. Apple nenutí MVVM jako jediný vzor, ale všechny vzdělávací materiály od roku 2019 používají ViewModel + SwiftUI. Pro UIKit Apple doporučuje MVC nebo Coordinator.
Android: viewModelScope automaticky ruší korutiny při čištění ViewModel. iOS: AnyCancellable z Combine se automaticky odhlásí při uvolnění objektu, který jej uchovává. SwiftUI @StateObject spravuje životní cyklus automaticky. Hlavní pravidla: neukládejte odkazy na View/Context ve ViewModel, rušte dlouho běžící operace při čištění, používejte weak self v uzávěrách.
StateFlow — moderní volba. LiveData je jednodušší a bezpečná pro životní cyklus, ale StateFlow je výkonnější: pracuje s korutinami, podporuje flatMap, combine, filter, nevyžaduje anotaci @Nullable. Jediný scénář, kde je LiveData preferovanější — práce s Java kódem, kde StateFlow (Kotlin Flow-API) není dostupný. Google doporučuje StateFlow pro nové projekty v Kotlin.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také