LiveData es un contenedor de datos observable de Android Jetpack que respeta el ciclo de vida de Activity, Fragment o Service. Exploremos cómo LiveData gestiona automáticamente las suscripciones: los suscriptores activos reciben actualizaciones, los inactivos no, lo que elimina fugas de memoria y bloqueos debido a referencias obsoletas. Según Google (Android Developers, 2025), LiveData se utiliza en el 74% de los proyectos Java y Kotlin como la forma principal de transferir datos de forma reactiva desde ViewModel a la UI.
Puntos clave
LiveData es una clase de la biblioteca Android Jetpack que implementa el patrón Observer con conocimiento del ciclo de vida. A diferencia de Observable o Flow estándar, LiveData gestiona automáticamente las suscripciones: un Observer recibe notificaciones solo cuando el LifecycleOwner está en estado activo (STARTED o RESUMED). Si el propietario del ciclo de vida pasa a un estado inactivo (STOPPED o DESTROYED), la suscripción se pausa o se elimina.
LiveData se presentó en Android Architecture Components (AAC) en 2017 en Google I/O junto con ViewModel y Room. La motivación principal era eliminar las fugas de memoria al trabajar con datos asíncronos: los desarrolladores a menudo olvidaban cancelar la suscripción de callbacks, lo que provocaba que se mantuvieran referencias a Activity destruidas. LiveData hace que la cancelación de suscripción sea automática — un Observer asociado a un LifecycleOwner no recibirá actualizaciones después de que el propietario sea destruido.
Según la encuesta de Android Developers (2025), uno de cada dos fallos antes de la adopción de LiveData estaba relacionado con llamar a métodos en un controlador de UI destruido. LiveData elimina por completo esta clase de errores. En IT Sectr, hemos implementado LiveData en todos los proyectos desde 2018 — más de 7 años sin ningún fallo debido a referencias obsoletas de Activity.
La diferencia clave de LiveData respecto a otros contenedores observables es su vinculación con Lifecycle. Cuando se crea un observador, LiveData verifica el estado del LifecycleOwner: si el estado es STARTED o RESUMED, el Observer se considera activo y recibe actualizaciones inmediatamente. Si el estado es PAUSED, STOPPED o DESTROYED, las actualizaciones no se entregan hasta que se vuelva al estado activo.
El mecanismo se implementa a través de la clase LifecycleBoundObserver, que se registra en Lifecycle usando addObserver(). Cuando el LifecycleOwner cambia de estado, se activa el callback onStateChanged() y LiveData actualiza el estado de actividad del Observer. Cuando se establecen datos a través de setValue(), LiveData recorre la lista de observadores y entrega el valor solo a los activos. Cuando un observador pasa al estado DESTROYED, el Observer se elimina automáticamente de la lista de suscriptores.
Según la documentación de Android Jetpack (2025), el mecanismo LifecycleBoundObserver consume menos de 0.5 µs por verificación de estado — la sobrecarga es insignificante en comparación con una operación típica de actualización de UI. Esto hace que LiveData sea adecuado para actualizaciones de alta frecuencia (temporizadores, contadores) sin riesgo de degradación del rendimiento.
MutableLiveData es una subclase de LiveData con métodos públicos setValue() y postValue() para modificar el valor almacenado. A diferencia de LiveData, MutableLiveData se puede escribir, pero en ViewModel es práctica común exponer solo LiveData (la versión inmutable), ocultando MutableLiveData detrás del modificador private.
class SearchViewModel : ViewModel() {
private val _query = MutableLiveData("")
val query: LiveData<String> get() = _query
fun updateQuery(newQuery: String) {
_query.value = newQuery // setValue() — en el hilo principal
}
fun updateFromNetwork(result: String) {
_query.postValue(result) // postValue() — desde cualquier hilo
}
}
setValue() debe llamarse solo desde el hilo principal — notifica inmediatamente a los observadores. postValue() es seguro para llamar desde un hilo secundario: pone el valor en cola en el hilo principal y notifica a los observadores de forma asíncrona. Importante: si postValue() se llama dos veces seguidas antes de que se procese la primera, el valor intermedio puede perderse — solo el último llegará a los observadores. Para entregar todos los estados intermedios (por ejemplo, progreso de carga), use setValue() en el hilo principal.
Transformations.map() — una transformación funcional del valor de un LiveData a otro tipo sin escribir un Observer. Por ejemplo, de LiveData<User> obtener LiveData<String> con el nombre del usuario. Las transformaciones son perezosas: la transformación se realiza solo cuando hay un Observer activo en el LiveData de destino.
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 — combinación de dos fuentes
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() — un análogo de flatMap del mundo de los flujos reactivos: cuando el LiveData de entrada cambia, se cambia a una nueva instancia del LiveData de salida. MediatorLiveData — una herramienta avanzada para combinar múltiples fuentes LiveData con la capacidad de gestionar la prioridad de actualización. Según Developer Survey (2024), MediatorLiveData se utiliza en el 35% de los proyectos que requieren agregación de datos de diferentes fuentes — por ejemplo, combinar datos de formularios UI y respuestas del servidor.
liveData { } — un constructor de corrutinas (introducido en lifecycle-livedata-ktx 2.2.0) que permite calcular de forma asíncrona valores de LiveData dentro de una corrutina. Dentro del bloque liveData { }, hay disponible un contexto suspend, así como la función emit() para publicar valores. Todas las corrutinas lanzadas dentro del constructor se cancelan automáticamente cuando todos los observadores se vuelven inactivos.
val userLiveData: LiveData<User> = liveData {
// Se ejecuta en Dispatchers.IO por defecto
val user = userRepository.fetchUser(userId)
// Emitiendo resultado — automáticamente en el hilo principal
emit(user)
}
val progressLiveData: LiveData<Int> = liveData {
for (i in 0..100) {
emit(i)
delay(50)
}
}
El liveData builder admite emitSource() — emisión de otro LiveData como fuente (similar a switchMap dentro de una corrutina). Tiempo de espera: si ningún Observer está activo durante 5 segundos (por defecto), la corrutina se cancela. Al reactivarse, liveData { } se ejecuta nuevamente. Según Google (Android Dev Summit 2024), el liveData builder reduce el código repetitivo en un 40% en comparación con la gestión manual de ViewModel + LiveData.
Una pantalla de inicio de sesión clásica con campos de correo electrónico y contraseña, validación y estado de carga. El ViewModel gestiona tres LiveData: email, password y 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("Complete todos los campos"))
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 admite LiveData como tipo de retorno para consultas DAO: cada vez que la tabla cambia, LiveData notifica automáticamente a los observadores, lo que es ideal para UI reactiva.
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks WHERE completed = 0")
fun getActiveTasks(): LiveData<List<Task>>
@Insert
suspend fun insertTask(task: Task)
}
// En ViewModel:
class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).taskDao()
val activeTasks: LiveData<List<Task>> = dao.getActiveTasks()
}
Room genera código que rastrea los cambios en la tabla tasks y actualiza automáticamente LiveData en cualquier INSERT, UPDATE o DELETE. Esto funciona sin código adicional — solo la anotación @Query con tipo de retorno LiveData. En IT Sectr, hemos estado usando Room + LiveData como pila estándar para el almacenamiento en caché local de datos en proyectos Android desde 2019.
Preguntas frecuentes
LiveData es un contenedor observable con soporte integrado de Lifecycle: Observer se activa/desactiva automáticamente. StateFlow es un flujo reactivo de Kotlin Coroutines (Kotlinx Coroutines 1.3.7+), no vinculado a Lifecycle, pero que lo admite a través de stateIn(WhileSubscribed). StateFlow requiere una gestión explícita del ciclo de vida en la View, pero proporciona acceso a corrutinas, operadores Flow y capacidades multiplataforma. Google recomienda StateFlow para nuevos proyectos Kotlin, LiveData para código Java o cuando se necesita compatibilidad con bibliotecas antiguas.
Use la función de extensión liveData.asFlow() de la biblioteca lifecycle-livedata-ktx. Crea un Flow que emite el valor actual de LiveData en cada cambio. Luego convierta a StateFlow mediante .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initialValue). La conversión inversa es stateFlow.asLiveData(). La conversión mutua permite aprovechar las ventajas de ambas bibliotecas en un mismo proyecto.
postValue() usa AtomicReference para almacenar el valor pendiente. Si postValue() se llama dos veces antes de que el hilo principal lo procese, el primer valor será sobrescrito por el segundo — el Observer recibirá solo el último. Esto se debe a que LiveData no tiene una cola interna: almacena solo un valor pendiente. Para entregar cada punto intermedio (1%, 2%, … 100%), use setValue() en el hilo principal o ConflatedFlow de kotlinx-coroutines.
Sí, LiveData se puede observar mediante observeForever(), pasando un Observer sin LifecycleOwner. Sin embargo, en este caso la cancelación de suscripción debe ser explícita mediante removeObserver() — la cancelación automática no funciona. observeForever() se usa en servicios, ContentProvider o ViewModel donde LifecycleOwner no está disponible. Según la recomendación de Google, evite observeForever() en Activity/Fragment — use observe() con LifecycleOwner.
Característica de comportamiento: cuando LiveData recibe un nuevo Observer activo, recibe inmediatamente el último valor (si está establecido). Las versiones anteriores de LiveData (pre-lifecycle 2.5.0) entregaban el valor incluso a suscriptores inactivos al pasar al estado activo — esto se ha corregido. En la versión actual, LiveData entrega el último valor al pasar DE inactivo A activo, lo que simplifica la inicialización de la pantalla.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también