Descripción general de LiveData Parte de Android Jetpack.

LiveData es una clase de contenedor de datos observables. 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 objetos Observer. 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 su 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 objeto LiveData se conecta al servicio del sistema una vez y, luego, cualquier observador que necesite el recurso puede simplemente mirar el objeto LiveData. 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:

  1. Crea una instancia de LiveData para contener un tipo de datos determinado. Por lo general, esto se hace dentro de la clase ViewModel.
  2. Crea un objeto Observer que defina el método onChanged(), el cual controla lo que sucede cuando cambian los datos retenidos del objeto LiveData. Por lo general, debes crear un objeto Observer en un controlador de IU, como una actividad o un fragmento.
  3. Conecta el objeto Observer al objeto LiveData con el método observe(). El método observe() toma un objeto LifecycleOwner. Esto suscribe el objeto Observer al objeto LiveData para que se le notifiquen los cambios. Normalmente, debes conectar el objeto Observer 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 objetos LiveData que está observando. Esto solo ocurre si se configuró el objeto LiveData 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, considera usar ejecutores junto 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 objeto LiveData 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 objeto LiveData no tiene ningún observador activo. Como ningún observador tiene implementado un objeto de escucha, no hay razón para mantenerse conectado al servicio StockManager.
  • El método setValue(T) actualiza el valor de la instancia de LiveData 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 objeto LiveData, y separa y despacha el resultado en sentido descendente. La función que se pasa a switchMap() debe mostrar un objeto LiveData, 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

Codelabs

Blogs

Videos