viewModelScope — egy beépített CoroutineScope az androidx.lifecycle könyvtárból, amely a ViewModel életciklusához van kötve és annak tisztításakor automatikusan törlődik. A Google Android Developers, 2025 szerint a viewModelScope a korutinok indításának szabványos mechanizmusa az MVVM architektúrában, biztonságos aszinkron műveleteket biztosítva memóriaszivárgás kockázata nélkül. A ViewModelScope alapértelmezésben Dispatchers.Main-t használ, és az összes IO műveletet benne a withContext-en keresztül kell végrehajtani.
Fő pontok
viewModelScope — egy kiterjesztési tulajdonság (extension property) a ViewModel interfészen, amely a lifecycle-viewmodel-ktx könyvtárban (2.1.0 verziótól) került hozzáadásra. Kész CoroutineScope-ot biztosít, amely a ViewModel életciklusához van kötve.
// Belső struktúra (egyszerűsített)
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope = this.getTag(JOB_KEY)
if (scope != null) return scope
return CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
.also { setTag(JOB_KEY, it) }
}
A scope lustán (lazy) jön létre az első hozzáféréskor, és a setTag-en keresztül gyorsítótárazódik. SupervisorJob kerül használatra, ami azt jelenti, hogy egy gyermek korutinban lévő kivétel nem törli a többit. Az alapértelmezett dispatcher a Dispatchers.Main.immediate, amely a főszálon hajtja végre a kódot további dispatch nélkül, ha a hívás már Main-en van.
Amikor a ViewModel elhagyja az életciklust (Activity befejeződött vagy Fragment eltávolításra került), a rendszer meghívja a clear()-t, amely elindítja az onCleared()-et. Ebben a callback-ben a viewModelScope törli a Job-ját, ami rekurzívan befejezi az összes aktív korutint. A mechanizmus a Closeable interfészen keresztül van implementálva, ahol a scope Job-ja automatikus lezárásra szánt erőforrásként van regisztrálva.
A viewModelScope ViewModel életciklusához való kapcsolódásának mechanizmusa címkézésen és az onCleared callback-en alapul. Nézzük meg lépésről lépésre, hogyan működik.
Amikor a ViewModel végrehajtja a viewModelScope.launch { ... }-t, a getter ellenőrzi, hogy létezik-e mentett scope a JOB_KEY címke alatt. Ha a scope még nem jött létre — létrejön egy új CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) példány. A scope a ViewModel-en belül egy belső címke térképen keresztül tárolódik.
A viewModelScope.launch vagy viewModelScope.async útján indított összes korutin a scope SupervisorJob-jának gyermekévé válik. A főszálon dolgoznak (ha nincs más dispatcher megadva a withContext-en keresztül). Amíg a ViewModel él — a korutinok lehetnek aktívak, felfüggesztettek vagy befejezettek.
Amikor a rendszer megsemmisíti a ViewModel-t, ViewModel.clear() kerül meghívásra. A clear()-en belül a következő történik:
Képernyőforgatáskor az Activity újra létrejön, de a ViewModel megmarad (a ViewModelStoreOwner-nek köszönhetően). Ez azt jelenti, hogy a viewModelScope aktív marad, és a korutinok megszakítás nélkül folytatódnak. Az Activity újralétrehozása után ugyanaz a ViewModel (és ugyanaz a scope) kerül felhasználásra — az adatbetöltés nem kezdődik újra.
MVVM (Model-View-ViewModel) — a Google által ajánlott architektúra Android alkalmazásokhoz. A viewModelScope központi helyet foglal el benne az aszinkron műveletek végrehajtójaként.
| Réteg | Komponens | A viewModelScope szerepe |
|---|---|---|
| UI | Activity / Fragment | Figyeli a StateFlow/LiveData-t a ViewModel-ből |
| ViewModel | ViewModel | Korutinokat indít a viewModelScope-on keresztül, kezeli az UI állapotát |
| Repository | Repository | Fogadja a viewModelScope korutinjaiból hívott suspend-függvényeket |
| Data | DAO / Api | Végrehajtja a tényleges kéréseket (Room, Retrofit) |
A ViewModel a viewModelScope-on keresztül korutinokat indít, amelyeken belül meghívja a Repository suspend-függvényeit. Az eredmény StateFlow-vá alakul, amelyet az UI réteg figyel. Ez a séma biztosítja a felelősségek egyértelmű elválasztását és az egyes rétegek független tesztelhetőségét.
Ha a korutinok a Fragment-ből indulnának, a képernyő forgatásakor a Fragment megsemmisítésével együtt törlődnének. A ViewModel túléli a forgatást, így a scope-jában indított korutinok folytatódnak. Ez a viewModelScope kulcsfontosságú előnye a lifecycleScope-pal szemben adatbetöltéskor.
Nézzünk meg három gyakorlati forgatókönyvet a viewModelScope használatára egy Android alkalmazásban Kotlin nyelven.
class ProfileViewModel(
private val repo: ProfileRepository
) : ViewModel() {
private val _profile = MutableStateFlow<Profile?>(null)
val profile: StateFlow<Profile?> = _profile
init {
loadProfile()
}
private fun loadProfile() {
viewModelScope.launch {
val result = repo.getProfile()
_profile.value = result
}
}
}
Az init blokkban azonnal elindul a profil betöltése. A korutin alapértelmezésben a főszálon fut. A repository a withContext(Dispatchers.IO)-t használja a hálózati kéréshez a suspend-függvényén belül, így a ViewModel nem foglalkozik szálváltással.
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<Item>) : UiState()
data class Error(val message: String) : UiState()
}
fun fetchItems() {
_state.value = UiState.Loading
viewModelScope.launch {
try {
val items = repo.getItems()
_state.value = UiState.Success(items)
} catch (e: Exception) {
_state.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
Az UI állapotot sealed class UiState írja le. A ViewModel minden változáskor frissíti a state-et. A Fragment fel van iratkozva a StateFlow-ra, és csak az aktuális állapotra reagál, figyelmen kívül hagyva az elavult hívásokat ismételt forgatásoknál.
private var searchJob: Job? = null
fun search(query: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300)
val results = repo.search(query)
_searchResults.value = results
}
}
Minden új keresési kérésnél az előző korutin törlődik. A delay(300) debounce-t valósít meg — a keresés csak 300 ms szünet után történik a bevitelben. Ez csökkenti a szerverterhelést és megakadályozza az elavult eredményeket.
Mindkét scope-ot az AndroidX Lifecycle könyvtár biztosítja, de különböző életciklusokhoz vannak kötve. A választás a feladat típusától függ.
| Jellemző | viewModelScope | lifecycleScope |
|---|---|---|
| Tulajdonos | ViewModel | LifecycleOwner (Activity/Fragment) |
| Törlés forgatáskor | Nem (ViewModel megmarad) | Igen (Activity újra létrejön) |
| Alapértelmezett dispatcher | Dispatchers.Main.immediate | Dispatchers.Main.immediate |
| Elérhető | ViewModel | Activity, Fragment, Service |
| Tipikus forgatókönyv | Adatbetöltés, üzleti logika | UI interakció, animációk, snackbar-ek |
A Google a viewModelScope használatát ajánlja minden adatbetöltéssel és feldolgozással kapcsolatos feladathoz. A lifecycleScope-ot az UI életének egy adott pillanatához kötött műveletekhez kell alkalmazni — például animáció indításához a képernyő első megjelenésekor vagy Location frissítésekre való feliratkozáshoz, amelynek meg kell szűnnie a képernyő elhagyásakor.
Még a jól dokumentált Android API-ban is előfordulnak tipikus hibák. Nézzünk meg négy gyakori problémát.
A legveszélyesebb hiba — a StateFlow vagy LiveData frissítésének kísérlete a ViewModel tisztítása után. Bár a viewModelScope törlődik az onCleared()-ben, a korutin kódot hajthat végre a tényleges törlés pillanatáig. Használja az isActive-t ellenőrzéshez, vagy támaszkodjon a catch blokk befejeződésére.
A viewModelScope belsőleg SupervisorJob-ot használ, ami elkülöníti a hibákat a korutinok között. De ha saját Job()-jal indít egy korutint a viewModelScope.launch-on belül, az a korutin a SupervisorJob gyermekévé válik, de nem lesz védve a törléstől más korutinok hibái esetén.
Bár a viewModelScope-nak nincs szigorú korlátja, több ezer aktív korutin lelassíthatja a rendszert. Hosszú adatlistákhoz használjon Flow-t a collectLatest-tel ahelyett, hogy külön korutinokat hozna létre minden elemhez.
Ha véletlenül GlobalScope-ot importál a viewModelScope helyett, a korutin nem törlődik a ViewModel tisztításakor. Ez memóriaszivárgáshoz és potenciális összeomláshoz vezet. Mindig ellenőrizze, hogy a korutinok viewModelScope-on keresztül indulnak, különösen örökölt fragmensekben.
Gyakran Ismételt Kérdések
A viewModelScope dispatcherét közvetlenül nem lehet megváltoztatni — szigorúan Dispatchers.Main.immediate-ként van beállítva. De a korutinon belül a withContext-en keresztül át lehet váltani másik dispatcherre. A dispatcher tesztelés közbeni megváltoztatásához használja a TestDispatcher-t a Rule-on keresztül.
Ne adja át a scope-ot a Repository-nak — ez sérti az architekturális elveket. A Repository-nak suspend-függvényeket kell biztosítania, a ViewModel pedig maga kezeli a korutinokat a viewModelScope-on keresztül. Ha a Repository scope-ot igényel — vizsgálja felül az architektúrát a Clean Architecture javára.
A SupervisorJob garantálja, hogy az egyik korutinban lévő kivétel (például több független kérés egyikének betöltési hibája) nem törli a többi korutint. Ez megfelel a ViewModel forgatókönyvének, ahol különböző képernyők független adatokat töltenek be.
Igen, a viewModelScope elérhető bármely ViewModel-ben, függetlenül az UI típusától (View System vagy Jetpack Compose). A Compose-ban a korutinok szintén a viewModelScope-on keresztül indulnak, az UI effektekhez pedig LaunchedEffect és rememberCoroutineScope használatos.
A viewModelScope.cancel() hívása azonnal törli a scope-ot — az összes aktív korutin CancellationException-nel végződik. Ha ezután meghívja a viewModelScope.launch-ot, egy új scope automatikusan létrejön a getter következő hozzáféréséné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