viewModelScope: ce este, legătura cu ViewModel și funcționarea în Android

Autor: IT Sectr Publicat: 2026-06-23 Timp de citire: 9 min

viewModelScope — este un CoroutineScope încorporat din biblioteca androidx.lifecycle, care este legat de ciclul de viață al ViewModel și se anulează automat la curățarea acestuia. Potrivit Google Android Developers, 2025, viewModelScope este mecanismul standard de lansare a corutinelor în arhitectura MVVM, asigurând lucrul sigur cu operații asincrone fără riscul de scurgeri de memorie. ViewModelScope folosește implicit Dispatchers.Main, iar toate operațiile IO din interior trebuie efectuate prin withContext.

Principalele puncte

  • viewModelScope — CoroutineScope din lifecycle-viewmodel-ktx, anulat la onCleared() al ViewModel
  • Dispatchers.Main — dispatcherul implicit, deci actualizările UI în corutine sunt sigure
  • onCleared — callback, la a cărui apelare viewModelScope anulează automat toate corutinele active
  • clear() vs onCleared() — clear() este apelat de framework înainte de onCleared, garantând anularea scope-ului
  • launch — metoda principală de lansare a corutinelor în viewModelScope pentru operații fire-and-forget

Ce este viewModelScope în Android?

viewModelScope — este o proprietate de extensie (extension property) pe interfața ViewModel, adăugată în biblioteca lifecycle-viewmodel-ktx (începând cu versiunea 2.1.0). Oferă un CoroutineScope gata făcut, legat de ciclul de viață al ViewModel.

kotlin
// Structura internă (simplificată)
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) }
    }

Scope-ul este creat leneș (lazy) la prima accesare și este stocat în cache prin setTag. Se folosește SupervisorJob, ceea ce înseamnă că o excepție într-o corutină copil nu le anulează pe celelalte. Dispatcherul implicit este Dispatchers.Main.immediate, care execută codul pe firul principal fără dispatchere suplimentară, dacă apelul este deja pe Main.

Cum primește viewModelScope notificarea de curățare

Când ViewModel părăsește ciclul de viață (Activity este finalizată sau Fragment este șters), sistemul apelează clear(), care declanșează onCleared(). În acest callback, viewModelScope își anulează Job-ul, ceea ce termină recursive toate corutinele active. Mecanismul este implementat prin interfața Closeable, unde Job-ul scope-ului este înregistrat ca resursă pentru închidere automată.

Cum funcționează viewModelScope: legătura cu ciclul de viață al ViewModel

Mecanismul de legare a viewModelScope la ciclul de viață al ViewModel se bazează pe etichetare și callback-ul onCleared. Să analizăm pas cu pas cum funcționează.

Pasul 1: Crearea scope-ului la prima accesare

Când ViewModel execută viewModelScope.launch { ... }, getter-ul verifică dacă există un scope salvat sub eticheta JOB_KEY. Dacă scope-ul nu a fost încă creat — se creează o nouă instanță CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate). Scope-ul este stocat în interiorul ViewModel printr-o hartă internă de etichete.

Pasul 2: Ciclul de viață al corutinelor

Toate corutinele lansate prin viewModelScope.launch sau viewModelScope.async devin copii față de SupervisorJob al scope-ului. Ele rulează pe firul principal (dacă nu este specificat un alt dispatcher prin withContext). Cât timp ViewModel este activ — corutinele pot fi active, suspendate sau finalizate.

Pasul 3: Anularea la onCleared

Când sistemul distruge ViewModel, se apelează ViewModel.clear(). În interiorul clear() se întâmplă următoarele:

  • Apelarea onCleared() pentru logica utilizatorului
  • Închiderea tuturor resurselor Closeable înregistrate prin addCloseable
  • Job-ul viewModelScope trece în starea Cancelled
  • Toate corutinele copil sunt anulate recursiv
  • Eliberarea referințelor la scope pentru colectorul de gunoi

Rezistența la rotația ecranului

La rotirea ecranului, Activity este recreată, dar ViewModel este păstrat (datorită ViewModelStoreOwner). Aceasta înseamnă că viewModelScope rămâne activ, iar corutinele continuă să ruleze fără întrerupere. După recrearea Activity, același ViewModel (și același scope) este reutilizat — încărcarea datelor nu începe de la zero.

viewModelScope în arhitectura MVVM

MVVM (Model-View-ViewModel) — este arhitectura recomandată de Google pentru aplicațiile Android. viewModelScope ocupă un loc central în ea ca executor al operațiilor asincrone.

Rolul viewModelScope în straturile arhitecturii

StratComponentăRolul viewModelScope
UIActivity / FragmentObservă StateFlow/LiveData din ViewModel
ViewModelViewModelLansează corutine prin viewModelScope, gestionează starea UI
RepositoryRepositoryPrimește funcții suspend apelate din corutinele viewModelScope
DateDAO / ApiExecută cererile efective (Room, Retrofit)

ViewModel prin viewModelScope lansează corutine, în interiorul cărora apelează funcțiile suspend ale Repository. Rezultatul este transformat în StateFlow, care este observat de stratul UI. Această schemă asigură o separare clară a responsabilităților și testabilitatea fiecărui strat în mod independent.

De ce viewModelScope în ViewModel, nu în Fragment

Dacă corutinele ar fi lansate din Fragment, la rotirea ecranului ele ar fi anulate odată cu distrugerea Fragmentului. ViewModel supraviețuiește rotației, deci corutinele lansate în scope-ul său continuă execuția. Acesta este avantajul cheie al viewModelScope față de lifecycleScope la încărcarea datelor.

Exemple de utilizare a viewModelScope

Să analizăm trei scenarii practice de utilizare a viewModelScope într-o aplicație Android în Kotlin.

Exemplul 1: Încărcarea datelor la crearea ViewModel

kotlin
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
        }
    }
}

În blocul init se lansează imediat încărcarea profilului. Corutina rulează pe firul principal (implicit). Repository-ul folosește withContext(Dispatchers.IO) pentru cererea de rețea în interiorul funcției sale suspend, așadar ViewModel nu se ocupă de comutarea firelor.

Exemplul 2: Gestionarea erorilor prin sealed class

kotlin
sealed class UiState {
    object Loading : UiState()
    data class Success(val data: List<Item>) : UiState()
    data class Error(val message: String) : UiState()
}
kotlin
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")
        }
    }
}

Starea UI este descrisă prin sealed class UiState. ViewModel actualizează state-ul la fiecare modificare. Fragment este abonat la StateFlow și reacționează doar la starea curentă, ignorând apelurile învechite la rotații repetate.

Exemplul 3: Anularea corutinei anterioare la o nouă cerere

kotlin
private var searchJob: Job? = null

fun search(query: String) {
    searchJob?.cancel()
    searchJob = viewModelScope.launch {
        delay(300)
        val results = repo.search(query)
        _searchResults.value = results
    }
}

La fiecare nouă cerere de căutare, corutina anterioară este anulată. delay(300) implementează debounce — căutarea se execută doar după 300 ms pauză în introducere. Aceasta reduce încărcarea serverului și previne rezultatele învechite.

viewModelScope vs lifecycleScope: când să alegem ce

Ambele scope-uri sunt furnizate de biblioteca AndroidX Lifecycle, dar sunt legate de cicluri de viață diferite. Alegerea între ele depinde de tipul sarcinii.

Compararea scope-urilor

CaracteristicăviewModelScopelifecycleScope
ProprietarViewModelLifecycleOwner (Activity/Fragment)
Anulare la rotațieNu (ViewModel este păstrat)Da (Activity este recreată)
Dispatcher implicitDispatchers.Main.immediateDispatchers.Main.immediate
Disponibil înViewModelActivity, Fragment, Service
Scenariu tipicÎncărcare date, logică de businessInteracțiune UI, animații, snackbar-uri

Recomandări Google

Google recomandă utilizarea viewModelScope pentru toate sarcinile legate de încărcarea și procesarea datelor. lifecycleScope trebuie aplicat pentru operațiile legate de un moment specific al vieții UI — de exemplu, lansarea unei animații la prima apariție a ecranului sau abonarea la actualizări Location care trebuie să înceteze la părăsirea ecranului.

Erori tipice la lucrul cu viewModelScope

Chiar și în API-ul bine documentat al Android, dezvoltătorii fac greșeli caracteristice. Să analizăm patru probleme frecvente.

Eroarea 1: Actualizarea UI după anularea scope-ului

Cea mai perfidă eroare — încercarea de a actualiza StateFlow sau LiveData după ce ViewModel a fost curățat. Deși viewModelScope este anulat la onCleared(), corutina poate executa cod până la momentul anulării efective. Folosiți isActive pentru verificare sau bazați-vă pe finalizarea blocului catch.

Eroarea 2: Lansarea corutinelor fără a ține cont de SupervisorJob

viewModelScope folosește intern SupervisorJob, ceea ce izolează erorile între corutine. Dar dacă lansați o corutină cu propriul Job() în interiorul viewModelScope.launch, această corutină devine copil al SupervisorJob, dar nu va fi protejată de anulare la erori în alte corutine.

Eroarea 3: Prea multe corutine într-un singur scope

Deși viewModelScope nu are o limită strictă, mii de corutine active pot încetini sistemul. Pentru liste lungi de date, folosiți Flow cu collectLatest în loc să creați corutine separate pentru fiecare element.

Eroarea 4: Utilizarea GlobalScope în loc de viewModelScope

Dacă importați din greșeală GlobalScope în loc de viewModelScope, corutina nu va fi anulată la curățarea ViewModel. Aceasta duce la scurgeri de memorie și potențiale crash-uri. Verificați întotdeauna că corutinele sunt lansate prin viewModelScope, în special în fragmentele moștenite.

Întrebări frecvente

Se poate schimba dispatcherul implicit al viewModelScope?

Nu se poate schimba direct dispatcherul viewModelScope — este setat rigid ca Dispatchers.Main.immediate. Dar în interiorul corutinei se poate comuta pe un alt dispatcher prin withContext. Pentru schimbarea dispatcherului în teste, folosiți TestDispatcher prin Rule.

Cum se transmite viewModelScope în Repository?

Nu transmiteți scope-ul în Repository — aceasta încalcă principiile arhitecturii. Repository trebuie să furnizeze funcții suspend, iar ViewModel gestionează singur corutinele prin viewModelScope. Dacă Repository necesită scope — revizuiți arhitectura în favoarea Clean Architecture.

De ce viewModelScope folosește SupervisorJob?

SupervisorJob garantează că o excepție într-o corutină (de exemplu, eroarea de încărcare a uneia dintre mai multe cereri independente) nu anulează celelalte corutine. Acest lucru corespunde scenariului ViewModel, unde ecrane diferite încarcă date independente.

Este viewModelScope disponibil în Jetpack Compose?

Da, viewModelScope este disponibil în orice ViewModel, indiferent de tipul UI (View System sau Jetpack Compose). În Compose, corutinele sunt de asemenea lansate prin viewModelScope, iar pentru efectele UI se folosesc LaunchedEffect și rememberCoroutineScope.

Ce se întâmplă cu corutina la apelarea viewModelScope.cancel()?

Apelarea viewModelScope.cancel() anulează scope-ul imediat — toate corutinele active se termină cu CancellationException. Dacă după aceasta apelați viewModelScope.launch, un nou scope este creat automat la următoarea accesare a getter-ului.

Concluzii

  • viewModelScope — CoroutineScope legat de ciclul de viață al ViewModel, anulat automat la onCleared()
  • SupervisorJob + Dispatchers.Main — configurația internă care asigură izolarea erorilor și accesul sigur la UI
  • Rotația ecranului — ViewModel este păstrat, deci corutinele în viewModelScope continuă fără repornire
  • Arhitectura MVVM — viewModelScope este elementul central pentru operațiile asincrone în stratul ViewModel
  • lifecycleScope — alternativă pentru operații legate de ciclul de viață al Activity/Fragment, nu al ViewModel
  • StateFlow — metoda preferată de transmitere a datelor din corutinele viewModelScope către UI prin sealed class
  • GlobalScope este periculos — înlocuirea viewModelScope cu GlobalScope duce la scurgeri de memorie și căderi ale aplicației

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și