ViewModel — Android Jetpack Architecture komponens, amely az Activity és Fragment életciklusának figyelembevételével UI-adatok tárolására és kezelésére szolgál. A Google I/O 2025 szerint a ViewModel a Jetpack-re épülő modern Android-alkalmazások 82%-ában használatos. A szokásos osztályokkal ellentétben a ViewModel automatikusan túléli a képernyő elforgatását és egyéb konfigurációs változásokat, megőrizve az UI állapotát adatvesztés nélkül. Az MVVM (Model-View-ViewModel) architektúra a ViewModel-re támaszkodik, mint központi rétegre, amely összeköti az üzleti logikát a felülettel.
Főbb pontok
ViewModel — egy osztály az Android Jetpack könyvtárból, amely a felhasználói felülethez kapcsolódó adatok tárolására és kezelésére szolgál, figyelembe véve az Activity vagy Fragment életciklusát. A ViewModel fő feladata, hogy elválassza az adat-előkészítési logikát az UI rétegtől, és megőrizze ezeket az adatokat konfigurációs változásoknál, mint a képernyő elforgatása, téma- vagy lokalizációváltás.
A ViewModel megjelenése előtt a fejlesztők az UI állapotát közvetlenül az Activity-ben vagy Fragment-ben tárolták. Képernyő elforgatásakor az Android elpusztítja az Activity-t és újat hoz létre — minden nem mentett adat elveszett. A megoldás az állapot mentése volt onSaveInstanceState() segítségével vagy onRetainNonConfigurationInstance() használata, de mindkét megközelítés kézi kezelést, szerializációt igényelt, és nem volt alkalmas összetett objektumokhoz. A ViewModel ezt a problémát keretrendszer szinten oldja meg: az adatok az UI-tól külön a memóriában élnek, és automatikusan visszatérnek az Activity újrateremtésekor.
Az Android Developers (2025) dokumentáció szerint a ViewModel a folyamat RAM-jában tárolja az adatokat — ez 10–50-szer gyorsabb, mint a Bundle-ből való helyreállítás onSaveInstanceState() segítségével, ahol bájt tömbbe szerializáció szükséges. A ViewModel minden olyan képernyőhöz ajánlott, ahol az adatok összetettebbek, mint egy egyszerű primitív vagy string.
A ViewModel életciklusa alapvetően különbözik az Activity életciklusától: a ViewModel nem semmisül meg a képernyő elforgatásakor, és a scope teljes befejezéséig él (Activity.finish() vagy Fragment removed). Ez azt jelenti, hogy a ViewModel-be betöltött adatok konfigurációs változás esetén is elérhetőek maradnak anélkül, hogy újra kellene tölteni őket a hálózatról vagy az adatbázisból.
Az Activity létrehozásakor a rendszer ViewModelProvider segítségével allokálja a ViewModel-t. Az első ViewModelProvider.get(ViewModel::class.java) hívásnál egy új ViewModel példány jön létre. A későbbi hívásoknál (beleértve az elforgatás utániakat is) ugyanaz a példány kerül visszaadásra. A ViewModel tisztítása automatikusan megtörténik az onCleared() hívásakor — ez a metódus akkor hívódik meg, amikor az Activity befejeződik (finish()) vagy a Fragment teljesen eltávolításra kerül. A fejlesztő felülbírálhatja az onCleared()-et az erőforrások felszabadításához: leiratkozás Flow-ról, korutinok megszakítása, socketek bezárása.
A Google a Jetpack dokumentációban hangsúlyozza: soha ne tároljon referenciát Activity-re vagy View-ra a ViewModel-en belül — ez memóriaszivárgáshoz vezet, mivel a ViewModel tovább él, mint az Activity az UI-val. Ehelyett használja a LiveData-t, StateFlow-t vagy SavedStateHandle-t az adatok ViewModel és UI közötti átviteléhez.
Az MVVM (Model-View-ViewModel) mintában a ViewModel központi helyet foglal el a View (Activity/Fragment) és a Model (repository, adatbázis, API) között. A View feliratkozik a ViewModel reaktív adataira (LiveData, StateFlow), és automatikusan frissül azok változásakor. A ViewModel nem tud a View létezéséről — csak adatokat és parancsokat szolgáltat, a View pedig eldönti, hogyan jelenítse meg azokat.
MVP és MVVM összehasonlítása: az MVP-ben a Presenter közvetlenül hívja a View (interface) metódusait, szoros kapcsolatot hozva létre. Az MVVM-ben a ViewModel reaktív adatfolyamokat tesz közzé, a View pedig feliratkozik rájuk — a kommunikáció egyirányú és tesztelhető. A JetBrains Developer Survey (2024) felmérés szerint az Android fejlesztők 68%-a használja az MVVM-et fő architektúraként, és a ViewModel ennek a mintának a kulcsfontosságú komponense.
Az IT Sectr-nél 2018 óta alkalmazzuk az MVVM-et ViewModel-lel az összes kereskedelmi Kotlin projektben. A gyakorlat azt mutatja, hogy ez a megközelítés 30–40%-kal csökkenti az UI logika hibakeresési idejét a felelősségek egyértelmű szétválasztásának és az üzleti logika emulátor nélküli tesztelhetőségének köszönhetően.
ViewModelProvider — a ViewModel beszerzésének szabványos módja fragment-ben vagy Activity-ben. Alapértelmezés szerint a ViewModelProvider üres konstruktoron keresztül hozza létre a ViewModel-t (argumentumok nélkül). Ha a ViewModel paramétereket igényel (például repository-t vagy alkalmazáskontextust), akkor a ViewModelProvider.Factory-t kell implementálni.
class UserViewModel(
private val userId: String,
private val repository: UserRepository
) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> get() = _user
fun loadUser() {
viewModelScope.launch {
_user.value = repository.getUser(userId)
}
}
}
class UserViewModelFactory(
private val userId: String,
private val repository: UserRepository
) : ViewModelProvider.Factory {
override fun create<T : ViewModel>(modelClass: Class<T>): T {
return UserViewModel(userId, repository) as T
}
}
A gyár a ViewModelProvider-nek kerül átadásra a ViewModel Fragment-ből vagy Activity-ből való beszerzésekor. SavedStateHandle — alternatív paraméterátadási mechanizmus, amely az AndroidX 1.2.0-ban jelent meg: a ViewModel automatikusan megkapja a SavedStateHandle-t a konstruktoron keresztül, és az argumentumok a Bundle-en keresztül kerülnek átadásra anélkül, hogy saját gyárat kellene írni.
viewModelScope — egy CoroutineScope, amely be van ágyazva a ViewModel-be és annak életciklusához van kötve. A viewModelScope-ben elindított összes korutin automatikusan törlődik az onCleared() hívásakor, megakadályozva a memóriaszivárgást és a háttérműveleteket a ViewModel megsemmisítése után.
class DashboardViewModel : ViewModel() {
private val _items = MutableLiveData<List<Item>>()
val items: LiveData<List<Item>> get() = _items
fun loadDashboard() {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.fetchDashboard()
withContext(Dispatchers.Main) {
_items.value = result
}
}
}
override fun onCleared() {
super.onCleared()
// A viewModelScope összes korutinja automatikusan törlődik
}
}
A viewModelScope-ben lévő korutinok alapértelmezés szerint a Dispatchers.Main-en futnak. Hálózati vagy lemez műveletekhez váltson Dispatchers.IO-ra a withContext segítségével, vagy adjon meg diszpécsert a launch-ban. A Google szerint (Android Dev Summit 2024) a viewModelScope használata 95%-kal csökkenti a korutinokkal kapcsolatos memóriaszivárgásokat a kézi Job kezeléshez képest.
Hilt — a hivatalos dependency injection könyvtár a Google-tól Android-hoz, a Dagger-re építve. A Hilt-tel nem kell kézzel megírni a ViewModelProvider.Factory-t — elég a ViewModel konstruktorát a @HiltViewModel annotációval ellátni. A Hilt automatikusan létrehozza a gyárat és beinjektálja a konstruktorban deklarált függőségeket.
@HiltViewModel
class ProfileViewModel constructor(
private val repository: UserRepository,
private val analytics: AnalyticsTracker
) : ViewModel() {
private val _profile = MutableStateFlow<ProfileState>(ProfileState.Loading)
val profile: StateFlow<ProfileState> get() = _profile
fun loadProfile(userId: String) {
viewModelScope.launch {
_profile.value = ProfileState.Success(repository.getUser(userId))
analytics.logEvent("profile_loaded")
}
}
}
// Fragment-ben — gyár nélkül:
val viewModel: ProfileViewModel = by viewModels()
Koin — alternatív DI könyvtár kódgenerálás nélkül. A Koin-ban a ViewModel a modulban kerül deklarálásra a viewModel { } segítségével, a fragment-ben pedig a by viewModel() segítségével szerezhető be. A Hilt és Koin közötti választás a projekttől függ: a Hilt a függőségi gráf ellenőrzését biztosítja fordítási időben, a Koin könnyebb és nem igényel kapt/ksp-t. Az IT Sectr-nél a Hilt-et nagy projektekben (több mint 50 képernyő), a Koin-t közepes projektekben használjuk.
A legegyszerűbb ViewModel, amely egy egész számlálót tárol, ami nem állítódik vissza a képernyő elforgatásakor. Bemutatja a MutableLiveData és LiveData használatának alapmintáját.
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> get() = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
fun reset() {
_count.value = 0
}
}
ViewModel, amely SavedStateHandle-t használ az állapot automatikus mentéséhez még akkor is, ha a folyamatot a rendszer megsemmisíti. A SavedStateHandle az egyetlen mechanizmus, amely megőrzi az adatokat az alkalmazás háttérbe kerülésekor és befejezésekor.
class FormViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userName = savedStateHandle.getLiveData<String>("userName", "")
val email = savedStateHandle.getLiveData<String>("email", "")
fun saveName(name: String) {
savedStateHandle["userName"] = name
}
fun saveEmail(email: String) {
savedStateHandle["email"] = email
}
}
LiveData a SavedStateHandle-ből automatikusan elmenti az utolsó értéket a Bundle-ben. A folyamat újrateremtésekor (például minimalizálás és az alkalmazás megölése után) a Bundle visszaállításra kerül, és a LiveData megkapja az előző értéket. A Google tesztek szerint a SavedStateHandle garantálja akár 5 KB adat mentését a Bundle-ben — elegendő szövegmezőkhöz, ID-khoz és szerializált JSON objektumokhoz.
Gyakran ismételt kérdések
ViewModel a folyamat RAM-jában tárolja az adatokat — azonnal elérhetőek szerializáció nélkül, alkalmas összetett objektumokhoz (listák, Bitmap, hálózati válaszok). Az onSaveInstanceState() szerializálja az adatokat Bundle-be (maximum 1 MB tranzakciónként Android 12-től) és csak egyszerű primitívekhez, String-hez és Serializable/Parcelable-hez alkalmas. ViewModel + SavedStateHandle — a Google által ajánlott kombináció: ViewModel a futásidejű adatokhoz, SavedStateHandle a folyamat megölésekor történő helyreállításhoz.
Nem, a rendszer automatikusan meghívja az onCleared()-et a scope befejezésekor. Kézi tisztítás a viewModelStore.clear() segítségével csak tesztekben szükséges a tesztesetek közötti szivárgások megelőzésére. Production kódban soha ne hívja a clear() metódust kézzel — ez megsérti a ViewModel életciklusát, és kiszámíthatatlan UI viselkedéshez vezethet.
Igen, a ViewModel teljes mértékben támogatott a Jetpack Compose-ban a viewModel() függvényen keresztül. A Compose-ban a ViewModel a Composable scope szintjén szerezhető be, és automatikusan tisztításra kerül a scope elhagyásakor. Az MVVM Compose verzióját Unidirectional Data Flow (UDF)-nak hívják: a ViewModel StateFlow-t tesz közzé, a Composable függvények pedig a collectAsState() segítségével iratkoznak fel. A reducer megközelítés Compose változata — MVI ViewModel-lel.
Tilos Activity, Fragment, View, Context (kivéve Application) referenciákat tárolni. Ez memóriaszivárgáshoz vezet, mivel a ViewModel tovább él, mint az UI kontextus. Ne tároljon szerializált View állapotokat (például RecyclerView pozíciót) — használja a LayoutManager.onSaveInstanceState()-et. Kerülje nagy mennyiségű adat tárolását (több mint 10 MB) — a folyamat minimalizálásakor az adatok elvesznek SavedStateHandle nélkül.
A ViewModel-t szokásos Kotlin osztályként tesztelik emulátor nélkül: hozzon létre egy példányt, hívja meg a metódusokat, ellenőrizze a LiveData vagy StateFlow állapotát. Korutinok teszteléséhez használja a runTest-et a kotlinx-coroutines-test-ből TestDispatcher-rel. Hilt-tel rendelkező ViewModel-hez használja a @HiltViewModelTest-et és a hiltViewModel()-t a teszt fragment-ben. A Google szerint az egységtesztek az ViewModel logika 80–90%-át lefedik instrumentációs tesztek nélkül.
Összefoglalás
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.
Olvassa el is