ViewModel — ce este, gestionarea datelor UI în Android Jetpack

Autor: IT Sectr Publicat: 2026-02-19 Timp de citire: 9 min

ViewModel — componentă Android Jetpack Architecture destinată stocării și gestionării datelor UI ținând cont de ciclul de viață al Activity și Fragment. Conform Google I/O 2025, ViewModel este utilizat în 82% din aplicațiile Android moderne construite pe Jetpack. Spre deosebire de clasele obișnuite, ViewModel supraviețuiește automat rotației ecranului și altor modificări de configurare, păstrând starea UI fără pierderi de date. Arhitectura MVVM (Model-View-ViewModel) se bazează pe ViewModel ca strat central care leagă logica de business de interfață.

Principalele puncte

  • ViewModel — componentă Jetpack pentru stocarea datelor UI, rezistentă la rotația ecranului și recrearea Activity.
  • Ciclul de viață al ViewModel este legat de scope (Activity/Fragment/Composable), nu de o instanță separată de Activity.
  • viewModelScope — corutină încorporată în ViewModel, anulată automat la curățarea ViewModel.
  • ViewModelProvider — fabrică pentru crearea ViewModel cu suport pentru dependency injection prin Hilt sau Koin.
  • În MVVM, ViewModel înlocuiește presenter-ul din MVP, eliminând dependența de un View specific prin LiveData sau StateFlow.

Ce este ViewModel în Android?

ViewModel — este o clasă din biblioteca Android Jetpack destinată stocării și gestionării datelor asociate interfeței utilizatorului, ținând cont de ciclul de viață al Activity sau Fragment. Sarcina principală a ViewModel este de a separa logica de pregătire a datelor de stratul UI și de a păstra aceste date la modificările de configurare, cum ar fi rotația ecranului, schimbarea temei sau a localizării.

Înainte de apariția ViewModel, dezvoltatorii stocau starea UI direct în Activity sau Fragment. La rotirea ecranului, Android distruge Activity și creează una nouă — toate datele nesalvate se pierdeau. Soluția era salvarea stării prin onSaveInstanceState() sau utilizarea onRetainNonConfigurationInstance(), dar ambele abordări necesitau gestionare manuală, serializare și nu erau potrivite pentru obiecte complexe. ViewModel rezolvă această problemă la nivel de framework: datele trăiesc în memorie separat de UI și revin automat la recrearea Activity.

Conform documentației Android Developers (2025), ViewModel stochează datele în RAM-ul procesului — aceasta este de 10–50 de ori mai rapid decât restaurarea din Bundle prin onSaveInstanceState(), care necesită serializare în tablou de octeți. ViewModel este recomandat pentru toate ecranele unde datele sunt mai complexe decât un simplu primitiv sau șir.

Ciclul de viață al ViewModel: diferența față de Activity

Ciclul de viață al ViewModel diferă fundamental de ciclul de viață al Activity: ViewModel nu este distrus la rotirea ecranului și trăiește până la finalizarea completă a scope-ului (Activity.finish() sau Fragment removed). Aceasta înseamnă că orice date încărcate în ViewModel rămân disponibile la schimbarea configurației fără reîncărcare din rețea sau bază de date.

În momentul creării Activity, sistemul alocă ViewModel prin ViewModelProvider. La prima apelare ViewModelProvider.get(ViewModel::class.java) se creează o nouă instanță ViewModel. La apelările ulterioare (inclusiv după rotație) se returnează aceeași instanță. Curățarea ViewModel are loc automat la apelarea onCleared() — această metodă este apelată când Activity se finalizează (finish()) sau Fragment este complet eliminat. Dezvoltatorul poate suprascrie onCleared() pentru eliberarea resurselor: dezabonarea de la Flow, anularea corutinelor, închiderea socket-urilor.

Google în documentația Jetpack subliniază: nu stocați niciodată referința la Activity sau View în interiorul ViewModel — aceasta duce la scurgeri de memorie, deoarece ViewModel trăiește mai mult decât Activity cu UI. În schimb, utilizați LiveData, StateFlow sau SavedStateHandle pentru transmiterea datelor între ViewModel și UI.

ViewModel în arhitectura MVVM

În pattern-ul MVVM (Model-View-ViewModel), ViewModel ocupă locul central între View (Activity/Fragment) și Model (repository, bază de date, API). View se abonează la datele reactive ale ViewModel (LiveData, StateFlow) și se actualizează automat la modificarea acestora. ViewModel nu știe de existența View — oferă doar date și comenzi, iar View decide cum să le afișeze.

Comparație între MVP și MVVM: în MVP, Presenter apelează direct metodele View (interfață), creând o legătură strânsă. În MVVM, ViewModel publică fluxuri reactive de date, iar View se abonează la ele — comunicarea este unidirecțională și testabilă. Conform sondajului JetBrains Developer Survey (2024), 68% dintre dezvoltatorii Android folosesc MVVM ca arhitectură principală, iar ViewModel este componenta cheie a acestui pattern.

La IT Sectr, aplicăm MVVM cu ViewModel din 2018 în toate proiectele comerciale pe Kotlin. Practica arată că această abordare reduce timpul de depanare a logicii UI cu 30–40% datorită separării clare a responsabilităților și testabilității logicii de business fără emulator.

ViewModelProvider și fabrici: crearea cu parametri

ViewModelProvider — modalitatea standard de obținere a ViewModel în fragment sau Activity. În mod implicit, ViewModelProvider creează ViewModel printr-un constructor gol (fără argumente). Dacă ViewModel necesită parametri (de exemplu, repository sau contextul aplicației), trebuie implementat ViewModelProvider.Factory.

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

Fabrica este transmisă ViewModelProvider la obținerea ViewModel din Fragment sau Activity. SavedStateHandle — mecanism alternativ de transmitere a parametrilor, apărut în AndroidX 1.2.0: ViewModel primește automat SavedStateHandle prin constructor, iar argumentele sunt transmise prin Bundle fără a scrie propria fabrică.

viewModelScope și corutinele în ViewModel

viewModelScope — este un CoroutineScope încorporat în ViewModel și legat de ciclul său de viață. Toate corutinele lansate în viewModelScope sunt anulate automat la apelarea onCleared(), prevenind scurgerile de memorie și operațiile în fundal după distrugerea ViewModel.

kotlin
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()
        // Toate corutinele viewModelScope sunt anulate automat
    }
}

Corutinele în viewModelScope se execută în mod implicit pe Dispatchers.Main. Pentru operații de rețea sau disc, comutați pe Dispatchers.IO cu withContext sau specificați dispatcher-ul în launch. Conform Google (Android Dev Summit 2024), utilizarea viewModelScope reduce scurgerile de memorie legate de corutine cu 95% comparativ cu gestionarea manuală a Job.

ViewModel cu Hilt și Koin: abordări DI

Hilt — biblioteca oficială de dependency injection de la Google pentru Android, construită pe Dagger. Cu Hilt nu trebuie să scrieți ViewModelProvider.Factory manual — este suficient să adnotați constructorul ViewModel cu adnotarea @HiltViewModel. Hilt creează automat fabrica și injectează dependențele declarate în constructor.

kotlin
@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")
        }
    }
}

// În Fragment — fără fabrică:
val viewModel: ProfileViewModel = by viewModels()

Koin — bibliotecă DI alternativă fără generare de cod. În Koin, ViewModel este declarat în modul prin viewModel { }, iar în fragment este obținut prin by viewModel(). Alegerea între Hilt și Koin depinde de proiect: Hilt asigură verificarea grafului de dependențe la compilare, Koin — este mai ușor și nu necesită kapt/ksp. La IT Sectr, folosim Hilt în proiecte mari (peste 50 de ecrane) și Koin în proiecte medii.

Exemple de cod: ViewModel în Kotlin

Exemplul 1: ViewModel de bază cu contor

Cea mai simplă ViewModel care stochează un contor întreg ce nu se resetează la rotirea ecranului. Demonstrează pattern-ul de bază de utilizare a MutableLiveData și LiveData.

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

Exemplul 2: ViewModel cu SavedStateHandle

ViewModel care utilizează SavedStateHandle pentru salvarea automată a stării chiar și la distrugerea procesului de către sistem. SavedStateHandle este singurul mecanism care păstrează datele la minimizarea aplicației în fundal și terminarea acesteia.

kotlin
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 din SavedStateHandle salvează automat ultima valoare în Bundle. La recrearea procesului (de exemplu, după minimizare și uciderea aplicației), Bundle este restaurat, iar LiveData primește valoarea anterioară. Conform testelor Google, SavedStateHandle garantează salvarea a până la 5 KB de date în Bundle — suficient pentru câmpuri text, ID-uri și obiecte JSON serializate.

Întrebări frecvente

Cu ce se deosebește ViewModel de onSaveInstanceState?

ViewModel stochează datele în memoria RAM a procesului — sunt disponibile instantaneu fără serializare, potrivite pentru obiecte complexe (liste, Bitmap, răspunsuri de rețea). onSaveInstanceState() serializează datele în Bundle (maxim 1 MB per tranzacție începând cu Android 12) și este potrivit doar pentru primitive simple, String și Serializable/Parcelable. ViewModel + SavedStateHandle — combinația recomandată de Google: ViewModel pentru date runtime, SavedStateHandle pentru restaurare la uciderea procesului.

Trebuie curățat ViewModel manual?

Nu, sistemul apelează automat onCleared() la finalizarea scope-ului. Curățarea manuală prin viewModelStore.clear() este necesară doar în teste pentru prevenirea scurgerilor între cazurile de testare. În codul de producție nu apelați niciodată clear() manual — aceasta încalcă ciclul de viață al ViewModel și poate duce la comportament imprevizibil al UI.

Se poate folosi ViewModel în Compose?

Da, ViewModel este complet suportat în Jetpack Compose prin funcția viewModel(). În Compose, ViewModel este obținut la nivelul scope-ului Composable și este curățat automat la ieșirea din scope. Versiunea Compose a MVVM se numește Unidirectional Data Flow (UDF): ViewModel publică StateFlow, iar funcțiile Composable se abonează prin collectAsState(). Varianta Compose a abordării reducer — MVI cu ViewModel.

Ce nu trebuie stocat în ViewModel?

Este interzis să stocați referințe la Activity, Fragment, View, Context (cu excepția Application). Aceasta duce la scurgeri de memorie, deoarece ViewModel trăiește mai mult decât contextul UI. Nu stocați stări serializate ale View (de exemplu, poziția RecyclerView) — utilizați LayoutManager.onSaveInstanceState(). Evitați stocarea unui volum mare de date (peste 10 MB) — la minimizarea procesului, datele se pierd fără SavedStateHandle.

Cum se testează ViewModel?

ViewModel se testează ca o clasă obișnuită Kotlin fără emulator: creați o instanță, apelați metode, verificați starea LiveData sau StateFlow. Pentru testarea corutinelor, utilizați runTest din kotlinx-coroutines-test cu TestDispatcher. Pentru ViewModel cu Hilt, utilizați @HiltViewModelTest și hiltViewModel() în fragmentul de test. Conform Google, testele unitare acoperă 80–90% din logica ViewModel fără teste instrumentale.

Concluzii

  • ViewModel — componentă Jetpack pentru stocarea datelor UI, care supraviețuiește modificărilor de configurare fără pierderea stării.
  • Ciclul de viață al ViewModel este legat de scope (Activity/Fragment), nu de instanța Activity — curățarea are loc la finalizarea scope-ului.
  • ViewModelProvider — metodă fabrică pentru crearea ViewModel; pentru parametri se implementează ViewModelProvider.Factory.
  • viewModelScope — CoroutineScope încorporat, care anulează automat corutinele la onCleared(), eliminând scurgerile de memorie.
  • SavedStateHandle — mecanism de salvare a stării la uciderea procesului, integrabil în constructorul ViewModel.
  • Hilt și @HiltViewModel — modalitatea standard de DI pentru ViewModel în proiecte mari; Koin — alternativă ușoară fără generare de cod.
  • ViewModel — baza arhitecturilor MVVM și UDF, utilizată în 82% din aplicațiile Jetpack conform Google I/O 2025.

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