Descripción general de LiveData Parte de Android Jetpack.
LiveData
es una clase de contenedor de datos observable. A diferencia de un observable regular, LiveData está optimizado para ciclos de vida, lo que significa que respeta el ciclo de vida de otros componentes de las apps, como actividades, fragmentos o servicios. Esta optimización garantiza que LiveData solo actualice observadores de componentes de apps que tienen un estado de ciclo de vida activo.
LiveData considera que un observador, que está representado por la clase Observer
, está en estado activo si su ciclo de vida está en el estado STARTED
o RESUMED
. LiveData solo notifica a los observadores activos sobre las actualizaciones. Los observadores inactivos registrados para ver objetos LiveData
no reciben notificaciones sobre los cambios.
Puedes registrar un observador vinculado con un objeto que implemente la interfaz LifecycleOwner
. Esta relación permite quitar al observador cuando el estado del objeto Lifecycle
correspondiente cambia a DESTROYED
.
Esto es especialmente útil para actividades y fragmentos, ya que pueden observar objetos LiveData
de forma segura y no preocuparse por las filtraciones: las actividades y los fragmentos se anulan instantáneamente cuando se destruyen sus ciclos de vida.
Para obtener más información sobre cómo usar LiveData, consulta Cómo trabajar con objetos LiveData.
Ventajas de usar LiveData
El uso de LiveData brinda las siguientes ventajas:
- Garantiza que la IU coincida con el estado de los datos
- LiveData sigue el patrón del observador. LiveData notifica a los objetos
Observer
cuando cambian los datos subyacentes. Puedes consolidar tu código para actualizar la IU en esos objetosObserver
. De esa manera, no necesitas actualizar la IU cada vez que cambian los datos de la app porque el observador lo hace por ti. - Sin fugas de memoria
- Los observadores están vinculados a objetos
Lifecycle
y borran lo que crean cuando se destruye el ciclo de vida asociado. - Actividades detenidas para evitar las fallas
- Si el ciclo de vida del observador está inactivo, como en el caso de una actividad de la pila de actividades, no recibe ningún evento de LiveData.
- No más control manual del ciclo de vida
- Los componentes de IU solo observan los datos relevantes y no detienen ni reanudan la observación. LiveData se ocupa automáticamente de todo esto, ya que está al tanto de los cambios de estado del ciclo de vida relevantes mientras lleva a cabo la observación.
- Datos siempre actualizados
- Si un ciclo de vida queda inactivo, recibe los datos más recientes después de quedar activo de nuevo. Por ejemplo, una actividad que estuvo en segundo plano recibe los datos más recientes inmediatamente después de volver al primer plano.
- Cambios de configuración apropiados
- Una actividad o un fragmento que se vuelve a crear debido a un cambio de configuración, como la rotación del dispositivo, recibe de inmediato los datos disponibles más recientes.
- Compartir recursos
- Puedes extender un objeto
LiveData
con el patrón singleton para unir los servicios del sistema de modo que puedan compartirse en la app. El objetoLiveData
se conecta al servicio del sistema una vez y, luego, cualquier observador que necesite el recurso puede simplemente mirar el objetoLiveData
. Para obtener más información, consulta Cómo extender LiveData.
Cómo trabajar con objetos LiveData
Sigue estos pasos para trabajar con objetos LiveData
:
- Crea una instancia de
LiveData
para contener un tipo de datos determinado. Por lo general, esto se hace dentro de la claseViewModel
. - Crea un objeto
Observer
que defina el métodoonChanged()
, el cual controla lo que sucede cuando cambian los datos retenidos del objetoLiveData
. Por lo general, debes crear un objetoObserver
en un controlador de IU, como una actividad o un fragmento. Conecta el objeto
Observer
al objetoLiveData
con el métodoobserve()
. El métodoobserve()
toma un objetoLifecycleOwner
. Esto suscribe el objetoObserver
al objetoLiveData
para que se le notifiquen los cambios. Normalmente, debes conectar el objetoObserver
a un controlador de IU, como una actividad o un fragmento.
Cuando actualizas el valor almacenado en el objeto LiveData
, activa todos los observadores registrados, siempre que el LifecycleOwner
conectado esté en el estado activo.
LiveData permite que los observadores del controlador de IU se suscriban a actualizaciones. Cuando los datos retenidos por el objeto LiveData
cambian, la IU se actualiza automáticamente en respuesta a eso.
Crea objetos LiveData
LiveData es un wrapper que se puede usar con cualquier dato, incluidos los objetos que implementan Collections
, como List
. Por lo general, un objeto LiveData
se almacena dentro de un objeto ViewModel
, y se puede acceder a este a través de un método get, como se muestra en el siguiente ejemplo:
Kotlin
class NameViewModel : ViewModel() { // Create a LiveData with a String val currentName: MutableLiveData<String> by lazy { MutableLiveData<String>() } // Rest of the ViewModel... }
Java
public class NameViewModel extends ViewModel { // Create a LiveData with a String private MutableLiveData<String> currentName; public MutableLiveData<String> getCurrentName() { if (currentName == null) { currentName = new MutableLiveData<String>(); } return currentName; } // Rest of the ViewModel... }
Inicialmente, los datos de un objeto LiveData
no están configurados.
Puedes obtener más información sobre los beneficios y el uso de la clase ViewModel
en la Guía de ViewModel.
Observa objetos LiveData
En la mayoría de los casos, el método onCreate()
de un componente de la app es el lugar adecuado para comenzar a observar un objeto LiveData
por los siguientes motivos:
- Para garantizar que el sistema no realice llamadas redundantes desde el método
onResume()
de una actividad o un fragmento - Para garantizar que la actividad o el fragmento tenga datos que pueda mostrar tan pronto como quede activo. Tan pronto como un componente de la app se encuentra en el estado
STARTED
, recibe el valor más reciente de los objetosLiveData
que está observando. Esto solo ocurre si se configuró el objetoLiveData
a observar.
En general, LiveData solo brinda actualizaciones cuando los datos cambian, y solo a observadores activos. Una excepción a este comportamiento es que los observadores también reciben una actualización cuando cambian de un estado activo a un estado inactivo. Además, si el observador cambia de inactivo a activo por segunda vez, solo recibe una actualización si el valor cambió desde la última vez que estuvo activo.
En el siguiente código de ejemplo, se indica cómo comenzar a observar un objeto LiveData
:
Kotlin
class NameActivity : AppCompatActivity() { // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact private val model: NameViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Other code to setup the activity... // Create the observer which updates the UI. val nameObserver = Observer<String> { newName -> // Update the UI, in this case, a TextView. nameTextView.text = newName } // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. model.currentName.observe(this, nameObserver) } }
Java
public class NameActivity extends AppCompatActivity { private NameViewModel model; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Other code to setup the activity... // Get the ViewModel. model = new ViewModelProvider(this).get(NameViewModel.class); // Create the observer which updates the UI. final Observer<String> nameObserver = new Observer<String>() { @Override public void onChanged(@Nullable final String newName) { // Update the UI, in this case, a TextView. nameTextView.setText(newName); } }; // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. model.getCurrentName().observe(this, nameObserver); } }
Después de llamar a observe()
y pasar nameObserver
como parámetro, onChanged()
se invoca de inmediato y proporciona el valor más reciente almacenado en mCurrentName
.
Si el objeto LiveData
no estableció un valor en mCurrentName
, no se llama a onChanged()
.
Actualiza objetos LiveData
LiveData no tiene métodos disponibles públicamente para actualizar los datos almacenados. La clase MutableLiveData
expone los métodos setValue(T)
y postValue(T)
de forma pública; debes usarlos si necesitas editar el valor almacenado en un objeto LiveData
. Por lo general, MutableLiveData
se usa en ViewModel
y, luego, ViewModel
solo expone objetos LiveData
inmutables a los observadores.
Después de configurar la relación del observador, puedes actualizar el valor del objeto LiveData
, como se muestra en el siguiente ejemplo, que activa todos los observadores cuando el usuario presiona un botón:
Kotlin
button.setOnClickListener { val anotherName = "John Doe" model.currentName.setValue(anotherName) }
Java
button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String anotherName = "John Doe"; model.getCurrentName().setValue(anotherName); } });
Llamar a setValue(T)
en el ejemplo da como resultado que los observadores llamen a sus métodos onChanged()
con el valor John Doe
. En el ejemplo, se presiona un botón, pero se puede llamar a setValue()
o postValue()
para actualizar mName
por varias razones, incluso en respuesta a una solicitud de red o para completar la carga de una base de datos. En todos los casos, la llamada a setValue()
o postValue()
activa observadores y actualiza la IU.
Usa LiveData con Room
La biblioteca de persistencias de Room admite consultas observables, que muestran objetos LiveData
.
Las consultas observables se escriben como parte del objeto de acceso a la base de datos (DAO).
Room genera todo el código necesario para actualizar el objeto LiveData
cuando se actualiza una base de datos. El código generado ejecuta la consulta de manera asíncrona en un subproceso en segundo plano cuando es necesario. Este patrón es útil para mostrar los datos en una IU sincronizada con los datos almacenados en una base de datos. Puedes obtener más información sobre Room y DAO en la guía sobre la biblioteca de persistencias Room.
Usa corrutinas con LiveData
LiveData
incluye compatibilidad con corrutinas de Kotlin. Para obtener más información, consulta Cómo usar corrutinas de Kotlin con componentes de la arquitectura de Android.
LiveData en la arquitectura de una app
LiveData
se adapta al ciclo de vida y sigue el ciclo de vida de entidades como actividades y fragmentos. Usa LiveData
para establecer una comunicación entre estos propietarios del ciclo de vida y otros objetos con una vida útil diferente, como los objetos ViewModel
.
La principal responsabilidad de ViewModel
es cargar y administrar los datos relacionados con la IU, lo que lo convierte en un gran candidato para conservar objetos LiveData
. Crea objetos LiveData
en ViewModel
y úsalos para exponer el estado a la capa de IU.
Las actividades y los fragmentos no deben contener instancias de LiveData
, ya que su función es mostrar datos, no mantener estados. Además, a fin de que las actividades y los fragmentos no puedan contener datos, facilita la escritura de pruebas de unidades.
Es posible que quieras trabajar con objetos LiveData
en tu clase de capa de datos, pero no se diseñó LiveData
para controlar flujos de datos asíncronos. Aunque puedes usar transformaciones LiveData
y MediatorLiveData
para lograr esto, este enfoque tiene desventajas: la capacidad de combinar flujos de datos es muy limitada y todos los objetos LiveData
(incluidos los creados a través de transformaciones) se observan en el subproceso principal. El siguiente código es un ejemplo de cómo retener un LiveData
en Repository
puede bloquear el subproceso principal:
Kotlin
class UserRepository { // DON'T DO THIS! LiveData objects should not live in the repository. fun getUsers(): LiveData<List<User>> { ... } fun getNewPremiumUsers(): LiveData<List<User>> { return getUsers().map { users -> // This is an expensive call being made on the main thread and may // cause noticeable jank in the UI! users .filter { user -> user.isPremium } .filter { user -> val lastSyncedTime = dao.getLastSyncedTime() user.timeCreated > lastSyncedTime } } }
Java
class UserRepository { // DON'T DO THIS! LiveData objects should not live in the repository. LiveData<List<User>> getUsers() { ... } LiveData<List<User>> getNewPremiumUsers() { return Transformations.map(getUsers(), // This is an expensive call being made on the main thread and may cause // noticeable jank in the UI! users -> users.stream() .filter(User::isPremium) .filter(user -> user.getTimeCreated() > dao.getLastSyncedTime()) .collect(Collectors.toList())); } }
Si necesitas usar flujos de datos en otras capas de tu app, procura usar flujos de Kotlin y, luego, convertirlos a LiveData
en ViewModel
con asLiveData()
.
Obtén más información para usar Kotlin Flow
con LiveData
en este codelab.
Para las bases de código compiladas con Java, procura usar ejecutores en conjunto con devoluciones de llamada o RxJava
.
Cómo extender LiveData
LiveData considera que un observador está en estado activo si su ciclo de vida está en el estado STARTED
o RESUMED
. En el siguiente código de muestra, se describe cómo extender la clase LiveData
:
Kotlin
class StockLiveData(symbol: String) : LiveData<BigDecimal>() { private val stockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive() { stockManager.requestPriceUpdates(listener) } override fun onInactive() { stockManager.removeUpdates(listener) } }
Java
public class StockLiveData extends LiveData<BigDecimal> { private StockManager stockManager; private SimplePriceListener listener = new SimplePriceListener() { @Override public void onPriceChanged(BigDecimal price) { setValue(price); } }; public StockLiveData(String symbol) { stockManager = new StockManager(symbol); } @Override protected void onActive() { stockManager.requestPriceUpdates(listener); } @Override protected void onInactive() { stockManager.removeUpdates(listener); } }
La implementación del oyente de precio en este ejemplo incluye los siguientes métodos importantes:
- El método
onActive()
se llama cuando el objetoLiveData
tiene un observador activo. Esto quiere decir que debes comenzar a observar las actualizaciones de cotización de las acciones a partir de este método. - El método
onInactive()
se llama cuando el objetoLiveData
no tiene ningún observador activo. Como ningún observador tiene implementado un objeto de escucha, no hay razón para mantenerse conectado al servicioStockManager
. - El método
setValue(T)
actualiza el valor de la instancia deLiveData
y notifica a los observadores activos sobre el cambio.
Puedes usar la clase StockLiveData
de la siguiente manera:
Kotlin
public class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val myPriceListener: LiveData<BigDecimal> = ... myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? -> // Update the UI. }) } }
Java
public class MyFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); LiveData<BigDecimal> myPriceListener = ...; myPriceListener.observe(getViewLifecycleOwner(), price -> { // Update the UI. }); } }
El método observe()
pasa el LifecycleOwner
asociado con la vista del fragmento como primer argumento. Hacer esto indica que este observador está vinculado al objeto Lifecycle
asociado con el propietario, lo que significa lo siguiente:
- Si el objeto
Lifecycle
no está en estado activo, no se llama al observador, aunque cambie el valor. - Después de que se destruye el objeto
Lifecycle
, el observador se quita automáticamente.
El hecho de que los objetos LiveData
tengan en cuenta el ciclo de vida significa que puedes compartirlos entre varias actividades, fragmentos y servicios. Para que el ejemplo sea simple, puedes implementar la clase LiveData
como un singleton de la siguiente manera:
Kotlin
class StockLiveData(symbol: String) : LiveData<BigDecimal>() { private val stockManager: StockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive() { stockManager.requestPriceUpdates(listener) } override fun onInactive() { stockManager.removeUpdates(listener) } companion object { private lateinit var sInstance: StockLiveData @MainThread fun get(symbol: String): StockLiveData { sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol) return sInstance } } }
Java
public class StockLiveData extends LiveData<BigDecimal> { private static StockLiveData sInstance; private StockManager stockManager; private SimplePriceListener listener = new SimplePriceListener() { @Override public void onPriceChanged(BigDecimal price) { setValue(price); } }; @MainThread public static StockLiveData get(String symbol) { if (sInstance == null) { sInstance = new StockLiveData(symbol); } return sInstance; } private StockLiveData(String symbol) { stockManager = new StockManager(symbol); } @Override protected void onActive() { stockManager.requestPriceUpdates(listener); } @Override protected void onInactive() { stockManager.removeUpdates(listener); } }
Además, puedes usarlo en el fragmento de la siguiente manera:
Kotlin
class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? -> // Update the UI. }) }
Java
public class MyFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); StockLiveData.get(symbol).observe(getViewLifecycleOwner(), price -> { // Update the UI. }); } }
Múltiples fragmentos y actividades pueden observar la instancia MyPriceListener
.
LiveData solo se conecta al servicio del sistema si uno o más de ellos están visibles y activos.
Transforma LiveData
Es posible que desees realizar cambios en el valor almacenado en un objeto LiveData
antes de despacharlo a los observadores, o tal vez debas mostrar una instancia de LiveData
diferente según el valor de otro. El paquete Lifecycle
proporciona la clase Transformations
, que incluye métodos de ayuda que admiten estas situaciones.
Transformations.map()
- Aplica una función al valor almacenado en el objeto
LiveData
y propaga el resultado de manera descendente.
Kotlin
val userLiveData: LiveData<User> = UserLiveData() val userName: LiveData<String> = userLiveData.map { user -> "${user.name} ${user.lastName}" }
Java
LiveData<User> userLiveData = ...; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });
Transformations.switchMap()
- De manera similar a
map()
, aplica una función al valor almacenado en el objetoLiveData
, y separa y despacha el resultado en sentido descendente. La función que se pasa aswitchMap()
debe mostrar un objetoLiveData
, como se indica en el siguiente ejemplo:
Kotlin
private fun getUser(id: String): LiveData<User> { ... } val userId: LiveData<String> = ... val user = userId.switchMap { id -> getUser(id) }
Java
private LiveData<User> getUser(String id) { ...; } LiveData<String> userId = ...; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
Puedes usar métodos de transformación para transportar información durante el ciclo de vida del observador. Las transformaciones no se calculan a menos que un observador esté viendo el objeto LiveData
que se mostró. Debido a que las transformaciones se calculan lentamente, el comportamiento relacionado con el ciclo de vida se traslada de manera implícita sin requerir llamadas o dependencias explícitas adicionales.
Si crees que necesitas un objeto Lifecycle
dentro de un objeto ViewModel
, una transformación probablemente sea una mejor solución. Por ejemplo, imagina que tienes un componente de IU que acepta una dirección y devuelve el código postal de esa dirección. Puedes implementar el ViewModel
básico para este componente como se indica en el siguiente código de muestra:
Kotlin
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() { private fun getPostalCode(address: String): LiveData<String> { // DON'T DO THIS return repository.getPostCode(address) } }
Java
class MyViewModel extends ViewModel { private final PostalCodeRepository repository; public MyViewModel(PostalCodeRepository repository) { this.repository = repository; } private LiveData<String> getPostalCode(String address) { // DON'T DO THIS return repository.getPostCode(address); } }
Luego, el componente de la IU debe anular el registro del objeto LiveData
anterior y registrarse en la nueva instancia cada vez que llama a getPostalCode()
. Además, si se vuelve a crear el componente de la IU, se activa otra llamada al método repository.getPostCode()
en lugar de usar el resultado de la llamada anterior.
En su lugar, puedes implementar la búsqueda del código postal como una transformación de la entrada de dirección. Eso se muestra en el siguiente ejemplo:
Kotlin
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() { private val addressInput = MutableLiveData<String>() val postalCode: LiveData<String> = addressInput.switchMap { address -> repository.getPostCode(address) } private fun setInput(address: String) { addressInput.value = address } }
Java
class MyViewModel extends ViewModel { private final PostalCodeRepository repository; private final MutableLiveData<String> addressInput = new MutableLiveData(); public final LiveData<String> postalCode = Transformations.switchMap(addressInput, (address) -> { return repository.getPostCode(address); }); public MyViewModel(PostalCodeRepository repository) { this.repository = repository } private void setInput(String address) { addressInput.setValue(address); } }
En este caso, el campo postalCode
se define como una transformación de addressInput
. Siempre y cuando la app tenga un observador activo asociado con el campo postalCode
, el valor del campo se recalcula y se recupera cuando cambia addressInput
.
Este mecanismo permite que los niveles inferiores de la app creen objetos LiveData
que se calculan de forma diferida a pedido. Un objeto ViewModel
puede obtener fácilmente referencias a objetos LiveData
y, luego, definir reglas de transformación sobre ellos.
Crea transformaciones nuevas
Hay decenas de transformaciones específicas que pueden ser útiles para tu app, pero no se proporcionan de manera predeterminada. Para implementar tu propia transformación, puedes usar la clase MediatorLiveData
, que implementa objetos de escucha para otros objetos LiveData
y procesa los eventos emitidos por estos. MediatorLiveData
propaga correctamente su estado al objeto LiveData
de origen. Para obtener más información sobre este patrón, consulta la documentación de referencia de la clase Transformations
.
Fusiona varias fuentes de LiveData
MediatorLiveData
es una subclase de LiveData
que te permite combinar varias fuentes de LiveData. Los observadores de los objetos MediatorLiveData
se activan cada vez que cambia alguno de los objetos de origen originales de LiveData.
Por ejemplo, si tienes un objeto LiveData
en la IU que se puede actualizar desde una base de datos local o una red, puedes agregar las siguientes fuentes al objeto MediatorLiveData
:
- Un objeto
LiveData
asociado con los datos almacenados en la base de datos. - Un objeto
LiveData
asociado con los datos a los que se accede desde la red.
Tu actividad solo necesita observar el objeto MediatorLiveData
para recibir actualizaciones de ambas fuentes. Si deseas ver un ejemplo detallado, consulta la sección Apéndice: Cómo exponer el estado de la red de la Guía de arquitectura de apps.
Recursos adicionales
Para obtener más información sobre la clase LiveData
, consulta los siguientes recursos.
Ejemplos
- Sunflower, una app de demostración que muestra las prácticas recomendadas para usar los componentes de la arquitectura
Codelabs
- Android Room con View (Java) (Kotlin)
- Corrutinas avanzadas con LiveData y flujo de Kotlin
Blogs
- ViewModels y LiveData: patrones y antipatrones
- LiveData más allá de ViewModel : Patrones reactivos con transformaciones y MediatorLiveData
- LiveData con SnackBar, Navigation y otros eventos (el caso de SingleLiveEvent)
Videos
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Cómo usar las corrutinas de Kotlin con componentes optimizados para ciclos de vida
- Cómo manejar ciclos de vida con componentes optimizados para estos ciclos
- Cómo probar tu implementación de Paging