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 — 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 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.
Î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 — 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.
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 — 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.
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.
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.
@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.
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.
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 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.
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
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.
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.
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.
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.
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
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.
Citiți și