LiveData — un container de date observabil din Android Jetpack care ține cont de ciclul de viață al Activity, Fragment sau Service. Vom vedea cum LiveData gestionează automat abonamentele: abonații activi primesc actualizări, iar cei inactivi — nu, ceea ce elimină scurgerile de memorie și crash-urile cauzate de referințe învechite. Conform datelor Google (Android Developers, 2025), LiveData este utilizat în 74% dintre proiectele Java și Kotlin ca metodă principală de transmitere reactivă a datelor de la ViewModel la UI.
Principalele puncte
LiveData — este o clasă din biblioteca Android Jetpack care implementează pattern-ul Observer ținând cont de ciclul de viață. Spre deosebire de Observable sau Flow standard, LiveData gestionează automat abonamentele: Observer primește notificări doar când LifecycleOwner se află în stare activă (STARTED sau RESUMED). Dacă proprietarul ciclului de viață trece în stare inactivă (STOPPED sau DESTROYED), abonamentul este suspendat sau eliminat.
LiveData a fost introdus în Android Architecture Components (AAC) în 2017 la Google I/O împreună cu ViewModel și Room. Motivația principală — eliminarea problemelor de scurgeri de memorie la lucrul cu date asincrone: dezvoltatorii uitau adesea să se dezaboneze de la callback-uri, ceea ce ducea la menținerea referințelor către Activity distruse. LiveData automatizează dezabonarea — Observer asociat cu LifecycleOwner nu va primi actualizări după distrugerea proprietarului.
Conform sondajului Android Developers (2025), fiecare al doilea crash înainte de implementarea LiveData era legat de apelarea metodelor pe un controler UI distrus. LiveData elimină complet această clasă de erori. La IT Sectr am implementat LiveData în toate proiectele începând cu 2018 — în 7 ani niciun crash din cauza unei referințe învechite la Activity.
Diferența cheie a LiveData față de alte containere observabile — legătura cu Lifecycle. La crearea observatorului, LiveData verifică statusul LifecycleOwner: dacă statusul este STARTED sau RESUMED, Observer este considerat activ și primește actualizări imediat. Dacă statusul este PAUSED, STOPPED sau DESTROYED, actualizările nu sunt livrate până la revenirea în stare activă.
Mecanismul este implementat prin clasa LifecycleBoundObserver care se înregistrează în Lifecycle cu ajutorul addObserver(). Când LifecycleOwner schimbă starea, este apelat callback-ul onStateChanged(), iar LiveData actualizează statusul de activitate al Observer. La setarea datelor prin setValue(), LiveData parcurge lista de observatori și livrează valoarea doar celor activi. La trecerea observatorului în starea DESTROYED, Observer este eliminat automat din lista de abonați.
Conform documentației Android Jetpack (2025), mecanismul LifecycleBoundObserver consumă mai puțin de 0,5 µs pentru verificarea statusului — overhead-ul este neglijabil în comparație cu o operație tipică de actualizare UI. Acest lucru face LiveData potrivit pentru actualizări de înaltă frecvență (timer-e, contoare) fără riscul de scădere a performanței.
MutableLiveData — moștenește LiveData cu metodele publice setValue() și postValue() pentru modificarea valorii stocate. Spre deosebire de LiveData, MutableLiveData este disponibil pentru scriere, dar în ViewModel se obișnuiește să se publice doar LiveData (versiunea imutabilă), ascunzând MutableLiveData sub modificatorul private.
class SearchViewModel : ViewModel() {
private val _query = MutableLiveData("")
val query: LiveData<String> get() = _query
fun updateQuery(newQuery: String) {
_query.value = newQuery // setValue() — pe threadul principal
}
fun updateFromNetwork(result: String) {
_query.postValue(result) // postValue() — de pe orice thread
}
}
setValue() trebuie apelat doar din threadul principal (main thread) — notifică imediat observatorii. postValue() este sigur pentru apelarea din threadul de fundal: plasează valoarea în coada threadului principal și notifică observatorii asincron. Important: dacă postValue() este apelat de două ori consecutiv înainte de procesarea primului, valoarea intermediară poate fi pierdută — observatorii vor primi doar ultima. Pentru transmiterea tuturor stărilor intermediare (de exemplu, progresul încărcării) utilizați setValue() pe threadul principal.
Transformations.map() — transformarea funcțională a valorii unui LiveData în alt tip fără a scrie Observer. De exemplu, din LiveData<User> să obțineți LiveData<String> cu numele utilizatorului. Transformările sunt leneșe: transformarea se execută doar când există un Observer activ pe LiveData țintă.
val userLiveData: LiveData<User> = ...
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
"${user.firstName} ${user.lastName}"
}
val userIdLiveData: LiveData<String> = ...
val userDetails: LiveData<UserDetails> = Transformations.switchMap(userIdLiveData) { id ->
repository.getUserDetails(id)
}
// MediatorLiveData — combinarea a două surse
val mediator = MediatorLiveData<CombinedState>()
mediator.addSource(priceLiveData) { price ->
mediator.value = CombinedState(price, countLiveData.value)
}
mediator.addSource(countLiveData) { count ->
mediator.value = CombinedState(priceLiveData.value, count)
}
Transformations.switchMap() — analogul flatMap din lumea fluxurilor reactive: la modificarea LiveData de intrare, comută pe o nouă instanță a LiveData de ieșire. MediatorLiveData — instrument avansat pentru combinarea mai multor surse LiveData cu posibilitatea de a gestiona prioritatea actualizărilor. Conform Developer Survey (2024), MediatorLiveData este utilizat în 35% dintre proiectele care necesită agregarea datelor din diferite surse — de exemplu, combinarea datelor din formularul UI și răspunsul serverului.
liveData { } — coroutine builder (apărut în lifecycle-livedata-ktx 2.2.0) care permite calcularea asincronă a valorii LiveData în interiorul unei corutine. În interiorul blocului liveData { } este disponibil contextul suspend, precum și funcția emit() pentru publicarea valorilor. Toate corutinele lansate în interiorul builder-ului sunt anulate automat la inactivitatea tuturor observatorilor.
val userLiveData: LiveData<User> = liveData {
// Se execută pe Dispatchers.IO implicit
val user = userRepository.fetchUser(userId)
// Emite rezultatul — automat pe threadul principal
emit(user)
}
val progressLiveData: LiveData<Int> = liveData {
for (i in 0..100) {
emit(i)
delay(50)
}
}
liveData builder suportă emitSource() — emiterea unui alt LiveData ca sursă (analog switchMap în interiorul corutinei). Timeout: dacă niciun Observer nu este activ timp de 5 secunde (implicit), corutina este anulată. La reactivare, liveData { } se execută din nou. Conform Google (Android Dev Summit 2024), liveData builder reduce 40% din codul boilerplate în comparație cu gestionarea manuală ViewModel + LiveData.
Ecranul clasic de autentificare cu câmpurile email și parolă, validare și stare de încărcare. ViewModel gestionează trei LiveData: email, password și loginResult.
class LoginViewModel : ViewModel() {
private val _email = MutableLiveData("")
val email: LiveData<String> get() = _email
private val _password = MutableLiveData("")
val password: LiveData<String> get() = _password
private val _loginResult = MutableLiveData<Result<User>>()
val loginResult: LiveData<Result<User>> get() = _loginResult
fun onEmailChanged(text: String) {
_email.value = text
}
fun onPasswordChanged(text: String) {
_password.value = text
}
fun login() {
if (_email.value.isNullOrBlank() || _password.value.isNullOrBlank()) {
_loginResult.value = Result.failure(IllegalArgumentException("Completați toate câmpurile"))
return
}
viewModelScope.launch {
try {
val user = authRepository.login(_email.value!!, _password.value!!)
_loginResult.value = Result.success(user)
} catch (e: Exception) {
_loginResult.value = Result.failure(e)
}
}
}
}
Room suportă LiveData ca tip de returnare a interogării DAO: la fiecare modificare a tabelului, LiveData notifică automat observatorii, ceea ce este ideal pentru UI reactiv.
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks WHERE completed = 0")
fun getActiveTasks(): LiveData<List<Task>>
@Insert
suspend fun insertTask(task: Task)
}
// În ViewModel:
class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).taskDao()
val activeTasks: LiveData<List<Task>> = dao.getActiveTasks()
}
Room generează cod care urmărește modificările în tabelul tasks și actualizează automat LiveData la fiecare INSERT, UPDATE sau DELETE. Acest lucru funcționează fără cod suplimentar — doar adnotarea @Query cu tipul de returnare LiveData. La IT Sectr folosim Room + LiveData ca stack standard pentru stocarea locală a datelor în proiectele Android din 2019.
Întrebări frecvente
LiveData — container observabil cu suport încorporat Lifecycle: Observer se activează/dezactivează automat. StateFlow — flux reactiv din Kotlin Coroutines (Kotlinx Coroutines 1.3.7+), nelegat de Lifecycle, dar care suportă acest lucru prin stateIn(WhileSubscribed). StateFlow necesită gestionarea explicită a ciclului de viață în View, dar oferă acces la corutine, operatori Flow și multi-platformă. Google recomandă StateFlow pentru proiecte noi în Kotlin, LiveData — pentru cod Java sau când este necesară compatibilitatea cu biblioteci vechi.
Utilizați funcția de extensie liveData.asFlow() din biblioteca lifecycle-livedata-ktx. Aceasta creează un Flow care emite valoarea curentă a LiveData la fiecare modificare. Apoi convertiți în StateFlow prin .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue). Conversia inversă — stateFlow.asLiveData(). Conversia reciprocă permite utilizarea avantajelor ambelor biblioteci în același proiect.
postValue() utilizează AtomicReference pentru stocarea valorii amânate. Dacă postValue() este apelat de două ori înainte de procesarea de către threadul principal, prima valoare va fi suprascrisă de a doua — Observable va primi doar ultima. Acest lucru se datorează faptului că LiveData nu are o coadă internă: stochează doar o singură valoare amânată. Pentru transmiterea fiecărui punct intermediar (1%, 2%, … 100%) utilizați setValue() pe threadul principal sau ConflatedFlow din kotlinx-coroutines.
Da, LiveData poate fi observat prin observeForever(), transmițând Observer fără LifecycleOwner. Însă în acest caz dezabonarea trebuie să fie explicită prin removeObserver() — dezabonarea automată nu funcționează. observeForever() se aplică în servicii, ContentProvider sau ViewModel unde LifecycleOwner nu este disponibil. Conform recomandării Google, evitați observeForever() în Activity/Fragment — utilizați observe() cu LifecycleOwner.
Caracteristică de comportament: când LiveData primește un nou Observer activ, acesta primește imediat ultima valoare (dacă este setată). Versiunile vechi de LiveData (înainte de lifecycle 2.5.0) livrau valoarea chiar și abonaților inactivi la trecerea în stare activă — acest lucru a fost reparat. În versiunea actuală, LiveData primește ultima valoare la trecerea DIN stare inactivă ÎN stare activă, ceea ce simplifică inițializarea ecranelor.
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