LiveData — спостережуваний контейнер даних з Android Jetpack, що враховує життєвий цикл Activity, Fragment або Service. Розберемо, як LiveData автоматично керує підписками: активні підписники отримують оновлення, а неактивні — ні, що усуває витоки пам'яті та краші через застарілі посилання. За даними Google (Android Developers, 2025), LiveData використовується в 74% проєктів на Java та Kotlin як основний спосіб реактивної передачі даних від ViewModel до UI.
Головне
LiveData — це клас з бібліотеки Android Jetpack, що реалізує патерн Observer з урахуванням життєвого циклу. На відміну від стандартних Observable або Flow, LiveData автоматично керує підписками: Observer отримує сповіщення тільки коли LifecycleOwner знаходиться в активному стані (STARTED або RESUMED). Якщо власник життєвого циклу переходить в неактивний стан (STOPPED або DESTROYED), підписка призупиняється або видаляється.
LiveData був представлений в Android Architecture Components (AAC) в 2017 році на Google I/O разом з ViewModel та Room. Основна мотивація — усунути проблему витоків пам'яті при роботі з асинхронними даними: розробники часто забували відписуватися від callback'ів, що призводило до утримання посилань на знищені Activity. LiveData робить відписку автоматичною — Observer, пов'язаний з LifecycleOwner, не отримає оновлення після знищення власника.
За даними опитування Android Developers (2025), кожен другий краш до впровадження LiveData був пов'язаний з викликом методів на знищеному UI-контролері. LiveData повністю усуває цей клас помилок. В IT Sectr ми впровадили LiveData у всі проєкти починаючи з 2018 року — за 7 років жодного крашу через застаріле посилання на Activity.
Ключова відмінність LiveData від інших observable-контейнерів — прив'язка до Lifecycle. При створенні спостерігача LiveData перевіряє статус LifecycleOwner: якщо статус STARTED або RESUMED, Observer вважається активним і отримує оновлення негайно. Якщо статус PAUSED, STOPPED або DESTROYED, оновлення не доставляються до повернення в активний стан.
Механізм реалізовано через клас LifecycleBoundObserver, який реєструється в Lifecycle за допомогою addObserver(). Коли LifecycleOwner змінює стан, спрацьовує callback onStateChanged(), і LiveData оновлює статус активності Observer. При встановленні даних через setValue() LiveData проходить по списку спостерігачів і доставляє значення тільки активним. При переході спостерігача в стан DESTROYED Observer автоматично видаляється зі списку підписників.
Згідно з документацією Android Jetpack (2025), механізм LifecycleBoundObserver споживає менше 0.5 мкс на перевірку статусу — накладні витрати мізерно малі порівняно з типовою операцією оновлення UI. Це робить LiveData придатним для високочастотних оновлень (таймери, лічильники) без ризику просідання продуктивності.
MutableLiveData — нащадок LiveData з відкритими методами setValue() та postValue() для зміни збереженого значення. На відміну від LiveData, MutableLiveData доступний для запису, але у ViewModel прийнято публікувати тільки LiveData (незмінну версію), приховуючи MutableLiveData під модифікатором private.
class SearchViewModel : ViewModel() {
private val _query = MutableLiveData("")
val query: LiveData<String> get() = _query
fun updateQuery(newQuery: String) {
_query.value = newQuery // setValue() — на main thread
}
fun updateFromNetwork(result: String) {
_query.postValue(result) // postValue() — з будь-якого потоку
}
}
setValue() повинен викликатися тільки з головного потоку (main thread) — він негайно сповіщає спостерігачів. postValue() безпечний для виклику з фонового потоку: він ставить значення в чергу головного потоку та сповіщає спостерігачів асинхронно. Важливо: якщо postValue() викликається двічі поспіль до обробки першого, проміжне значення може бути втрачене — до спостерігачів дійде тільки останнє. Для передачі всіх проміжних станів (наприклад, прогрес завантаження) використовуйте setValue() на головному потоці.
Transformations.map() — функціональне перетворення значення одного LiveData в інший тип без написання Observer. Наприклад, з LiveData<User> отримати LiveData<String> з ім'ям користувача. Трансформації ліниві: перетворення виконується тільки при наявності активного Observer на цільовому LiveData.
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 — об'єднання двох джерел
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() — аналог flatMap зі світу реактивних потоків: при зміні вхідного LiveData він перемикається на новий екземпляр вихідного LiveData. MediatorLiveData — просунутий інструмент для об'єднання декількох LiveData-джерел з можливістю керувати пріоритетом оновлень. За даними Developer Survey (2024), MediatorLiveData використовується в 35% проєктів, де потрібна агрегація даних з різних джерел — наприклад, об'єднання даних з UI-форми та відповіді сервера.
liveData { } — coroutine builder (з'явився в lifecycle-livedata-ktx 2.2.0), що дозволяє асинхронно обчислювати значення LiveData всередині корутини. Всередині блоку liveData { } доступний suspend-контекст, а також функція emit() для публікації значень. Всі корутини, запущені всередині builder, автоматично скасовуються при неактивності всіх спостерігачів.
val userLiveData: LiveData<User> = liveData {
// Виконується на Dispatchers.IO за замовчуванням
val user = userRepository.fetchUser(userId)
// Емітуємо результат — автоматично на main thread
emit(user)
}
val progressLiveData: LiveData<Int> = liveData {
for (i in 0..100) {
emit(i)
delay(50)
}
}
liveData builder підтримує emitSource() — емісію іншого LiveData як джерела (аналог switchMap всередині корутини). Тайм-аут: якщо жоден Observer не активний протягом 5 секунд (за замовчуванням), корутина скасовується. При повторній активації liveData { } виконується заново. За даними Google (Android Dev Summit 2024), liveData builder на 40% скорочує шаблонний код порівняно з ручним керуванням ViewModel + LiveData.
Класичний екран входу з полями email та пароль, валідацією та станом завантаження. ViewModel керує трьома LiveData: email, password та 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("Заповніть всі поля"))
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 підтримує LiveData як тип, що повертається DAO-запитом: при кожній зміні таблиці LiveData автоматично сповіщає спостерігачів, що ідеально для reactice UI.
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks WHERE completed = 0")
fun getActiveTasks(): LiveData<List<Task>>
@Insert
suspend fun insertTask(task: Task)
}
// У ViewModel:
class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).taskDao()
val activeTasks: LiveData<List<Task>> = dao.getActiveTasks()
}
Room генерує код, який відстежує зміни в таблиці tasks та автоматично оновлює LiveData при будь-якому INSERT, UPDATE або DELETE. Це працює без додаткового коду — тільки анотація @Query з типом, що повертається LiveData. В IT Sectr ми використовуємо Room + LiveData як стандартний стек для локального кешування даних в Android-проєктах з 2019 року.
Часто задавані питання
LiveData — спостережуваний контейнер із вбудованою підтримкою Lifecycle: Observer автоматично активується/деактивується. StateFlow — реактивний потік з Kotlin Coroutines (Kotlinx Coroutines 1.3.7+), не прив'язаний до Lifecycle, але підтримує це через stateIn(WhileSubscribed). StateFlow вимагає явного керування життєвим циклом у View, але дає доступ до корутин, операторів Flow та мультиплатформенності. Google рекомендує StateFlow для нових проєктів на Kotlin, LiveData — для Java-коду або при необхідності сумісності зі старими бібліотеками.
Використовуйте extension-функцію liveData.asFlow() з бібліотеки lifecycle-livedata-ktx. Вона створює Flow, що емітує поточне значення LiveData при кожній зміні. Потім перетворіть в StateFlow через .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue). Зворотне перетворення — stateFlow.asLiveData(). Взаємна конвертація дозволяє використовувати переваги обох бібліотек в одному проєкті.
postValue() використовує AtomicReference для зберігання відкладеного значення. Якщо postValue() викликаний двічі до обробки головним потоком, перше значення буде перезаписане другим — Observable отримає тільки останнє. Це пов'язано з тим, що LiveData не має внутрішньої черги: він зберігає тільки одне відкладене значення. Для передачі кожної проміжної точки (1%, 2%, … 100%) використовуйте setValue() на головному потоці або ConflatedFlow з kotlinx-coroutines.
Так, LiveData можна спостерігати через observeForever(), передаючи Observer без LifecycleOwner. Однак в цьому випадку відписка повинна бути явною через removeObserver() — автоматична відписка не працює. observeForever() застосовується в сервісах, ContentProvider або ViewModel, де LifecycleOwner недоступний. За рекомендацією Google, уникайте observeForever() в Activity/Fragment — використовуйте observe() з LifecycleOwner.
Особливість поведінки: коли LiveData отримує нового активного Observer, він негайно отримує останнє значення (якщо воно встановлено). Старі версії LiveData (до lifecycle 2.5.0) доставляли значення навіть неактивним підписникам при переході в активний стан — це виправлено. В актуальній версії LiveData отримує останнє значення при переході FROM неактивного TO активного стану, що спрощує ініціалізацію екранів.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також