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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также