Bienvenido, soy Miguel y en esta ocasión les traigo este nuevo tutorial.
Índice
Por qué debería usar LiveData, qué es y cómo funciona
Los componentes de arquitectura son comunes para los desarrolladores de Android en estos días. Durante mucho tiempo, todos han estado utilizando uno u otros componentes de la arquitectura para cumplir con sus requisitos.
LiveData es uno de los componentes más importantes. En este artículo, discutimos LiveData, cómo se usa y su importancia.
Anteriormente, antes de los componentes de arquitectura y Rx Java, ¿recuerda cómo pasamos los resultados o las devoluciones de llamada al UI thread
? Principalmente, con interfaces.
Tomemos un ejemplo simple de una aplicación desarrollada en MVP
. Anteriormente, si había que realizar una llamada a la API, llamaríamos a un método de la capa View
en el Presenter
para realizar una solicitud de red.
Luego, en ese método en particular, solíamos escribir el Async task
para realizar las operaciones. Una vez que recibimos la respuesta, usamos una View
interfaz para comunicar esta respuesta al hilo Main
para que pudiera actualizar la interfaz de usuario.
Aquí todo está estrechamente acoplado View y Presenter y se utiliza una gran cantidad de interfaces para la interacción entre capas.
La solución a esto vino con MVVM
, pero no vamos a discutir esto aquí. Basta decir que el problema principal de usar interfaces
ha sido resuelto por el concepto de LiveData
.
¿Qué es LiveData?
De la documentación:
LiveData es una clase de contenedor de datos observable . A diferencia de un observable normal, LiveData es consciente del ciclo de vida, lo que significa que respeta el ciclo de vida de otros componentes de la aplicación, como actividades, fragmentos o servicios.
Este conocimiento garantiza que LiveData solo actualice los observadores de componentes de la aplicación que se encuentran en un estado de ciclo de vida activo.
Para comprender LiveData, primero debemos comprender el patrón de observador. El observer pattern
es un patrón de diseño de software en el que un objeto, llamado sujeto, mantiene una lista de sus dependientes, llamados observadores, y les notifica automáticamente de cualquier cambio de estado, generalmente llamando a uno de sus métodos.
En términos simples, habrá un Observer
que se suscriba al Observable
para recibir notificaciones de los últimos datos o decir cambios de estado. Anteriormente usamos, Rx Java
pero aquí debemos mantener una lista subscriptions
y remove
en formato onDestroy()
.
Es un Observer
patrón. LiveData
es una observable
clase de soporte de datos: no es necesario solicitar los datos más recientes cada vez. Mientras que el uso de MVVM
la comunicación de ViewModel
a View
se realiza con solo LiveData
.
Desde la actividad View
o fragmento vamos subscribe
al LiveData
, pasando el lifecycle owner
y Observer
. Pero no es necesario mantener la lista o destruir las suscripciones. Como LiveData
es lifecycle-aware
que se encarga de estas cosas.
LiveData solo publica los valores en Observers
(actividad o Fragmento) si están en un foreground
estado, para que no se produzcan pérdidas de memoria. No hay necesidad para nosotros para comprobar si View
es alive
o no actualizar la interfaz de usuario dentro de la Observer
.
Ya no tenemos que preocuparnos por cancelar la suscripción onPause
o onDestroy
. Además, una vez que Observer
se reanude, se notificará de inmediato los últimos datos del LiveData
.
Las dos cosas importantes que debemos comprender LiveData
son:
- El controlador de UI o View (an
Activity
oFragment
) serán notificados cada vez que cambien los datos en lugar de solicitar los datos cada vez desdeViewModel
( ¡ Datos siempre actualizados !) - LiveData es
lifecycle-aware
, por lo que la vista se registrará para observar esos cambios solo siView
(unaActivity
oFragment
) está enSTARTED
oRESUMED
y solo entonces LiveData emitirá elementos (por lo que no habrá más fallas, manejo del ciclo de vida, fugas de memoria yNullPointerExceptions
debido a vistas inexistentes!)
Empecemos a codificar
Para trabajar con LiveData
necesitamos seguir los siguientes pasos:
- Declare dependencias en el nivel de la aplicación
build.gradle
. - Cree un
instance
LiveData para contener un cierto tipo de datos. Esto generalmente se hace dentro de la clase ViewModel. - Cree un
Observer
objeto que defina elonChanged()
método, que controle lo que sucede cuando cambian los datos retenidos del objeto LiveData. Por lo general, crea unObserver
objeto en un controlador de IU, como una actividad o un fragmento. - Adjunte el
Observer
objeto alLiveData
objeto usando elobserve()
método. Este método toma unLifecycleOwner
objeto y lo suscribeObserver
alLiveData
objeto para que se le notifique de los cambios. Por lo general, adjunta elObserver
objeto en un controlador de interfaz de usuario, como unactivity
ofragment
.
//Java implementation "android.arch.lifecycle:extensions:1.0.0" annotationProcessor "android.arch.lifecycle:compiler:1.0.0" //Kotlin with AndroidX implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
LiveData es un contenedor que se puede utilizar para almacenar cualquier tipo de datos, incluidos los objetos que implementan Collection
s, como List
. Un LiveData
objeto generalmente se almacena dentro de un ViewModel
objeto y se accede a él mediante un método getter.
package com.example.viewmodel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class SampleViewModel (val name:String): ViewModel() { private val _nameLiveData = MutableLiveData<String>() val nameLiveData: LiveData<String> get() = _nameLiveData fun setFirstName() { _nameLiveData.postValue( name) } }
Inicialmente, los datos en un objeto LiveData no se configuran más tarde cuando llamamos y setFirstName()
luego se actualizó el valor de LiveData.
Cree un Activity
LiveData y observe cualquier cambio como se muestra a continuación.
package com.example.viewmodel import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { lateinit var viewModel: SampleViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel = ViewModelProvider(this).get(SampleViewModel::class.java) observeViewModel() initListeners() } private fun initListeners() { btn_badge?.setOnClickListener { viewModel.setFirstName() } } private fun observeViewModel() { viewModel.nameLiveData.observe(this, Observer { showToast(it) }) } private fun showToast(value: String) { Toast.makeText(this, value, Toast.LENGTH_LONG).show() } }
Hay dos formas de actualizar un valor en LiveData.
-
setValue
-
postValue
Debemos usar el setValue(T)
método para actualizar el LiveData
objeto del main thread
. Si usamos setValue
un hilo en segundo plano, la aplicación se bloquea, se llama al registro desde la WongThread
excepción.
_nameLiveData.value = "something"
postValue
Si el código se ejecuta en un worker
hilo, que no sea el hilo principal, puede utilizar el postValue(T)
método para actualizar el LiveData
objeto. Se puede usar desde cualquier hilo, pero se prefiere desde otro hilo que no sea el principal porque es un poco más lento que setValue
.
Para actualizar el valor de LiveData use setValue desde Main Thready postValue de cualquier otro thread que no sea el hilo principal para un mejor rendimiento.
Digamos que hay una aplicación que tiene que realizar una llamada a la API cuando un usuario abre una actividad y hace clic en un botón. Después de obtener la respuesta con LiveData
, publicamos el valor en el controlador de la interfaz de usuario para actualizar la vista.
En este caso, mientras la llamada a la API está progress
activada, de alguna manera la aplicación pasa al background
estado. ¿Puedes adivinar qué pasa después? ¿Se actualizará la interfaz de usuario o se bloqueará la aplicación?
La llamada a la API se completa y LiveData
recibe el valor. Sin embargo, ni el bloqueo ni la interfaz de usuario se actualizan porque, como discutimos anteriormente, LiveData es lifecycle-aware
así que cuando finaliza la llamada a la API, LiveData recibe el valor pero no publica el valor en el controlador de la interfaz de usuario.
Esperará hasta que el controlador foreground
de la interfaz de usuario llegue al estado y luego LiveData publica inmediatamente el último valor disponible en el controlador de la interfaz de usuario.
Tipos de LiveData
Podemos ampliar LiveData<T>
y crear nuestra propia clase personalizada.
Código de MutableLiveData
, según la documentación:
package androidx.lifecycle; /** * {@link LiveData} which publicly exposes {setValue(T)} and {#postValue(T)} method. * * @param <T> The type of data hold by this instance */ @SuppressWarnings("WeakerAccess") public class MutableLiveData<T> extends LiveData<T> { @Override public void postValue(T value) { super.postValue(value); } @Override public void setValue(T value) { super.setValue(value); } }
Podemos fusionar resultados de diferentes LiveData
objetos en uno usando MediatorLiveData
. También se puede definir como la subclase LiveData, que puede observar otros objetos LiveData y reaccionar a los OnChanged
eventos de ellos.
Supongamos que tenemos dos objetos LiveData que emiten el valor del tipo de número. Un objeto LiveData emite valores de una fuente particular y el otro de una fuente diferente.
Queremos escuchar el resultado de ambos y también queremos descuidar el resultado del segundo LiveData si es menor de diez. Hacemos esto de la siguiente manera:
val mediatorLiveData: MediatorLiveData<Int> = MediatorLiveData() mediatorLiveData.addSource(stockLiveData1, Observer<Int> { it -> mediatorLiveData.setValue(it) }) mediatorLiveData.addSource(stockLiveData1, Observer<Int> { value -> if (value > 10) { mediatorLiveData.setValue(value) } })
Digamos que solo queremos liveData
que se fusionen cinco valores emitidos por MediatorLiveData
. Luego, después de esos cinco valores, podemos dejar de escuchar liveData
y eliminarlo como fuente:
val liveDataMerger: MediatorLiveData<Int> = MediatorLiveData() val liveData = MutableLiveData<Int>() val liveData2 = MutableLiveData<Int>() liveDataMerger.addSource(liveData) { value -> liveDataMerger.setValue(value) } liveDataMerger.addSource(liveData2) { value2-> var count = 1 count++ liveDataMerger.setValue(value2) if (count > 5) { liveDataMerger.removeSource(liveData2) } }
Si lo dado LiveData
ya se agregó como fuente, pero con una diferente Observer
, se lanzará IllegalArgumentException
.
Transformaciones
La clase Transformations
nos proporciona funciones con las que podemos cambiar el valor en nuestro objeto LiveData. Actualmente, tenemos 2 funciones disponibles en esta clase: map
y switchMap
.
map()
le permite aplicar cambios a cada valor a medida que los emite LiveData
. Este método es análogo al Rx(io.reactivex.Observable)
operador de mapa. La transformación se ejecutará en el hilo main
. Aquí hay un ejemplo de los documentos donde User
se asigna un objeto para obtener el nombre completo:
LiveData<User> userLiveData = ...; LiveData<String> userFullNameLiveData = Transformations.map(userLiveData,user -> user.firstName + user.lastName); });
Según la documentación, el código map
del método es el siguiente:
@MainThread public static <X, Y> LiveData<Y> map( @NonNull LiveData<X> source, @NonNull final Function<X, Y> mapFunction) { final MediatorLiveData<Y> result = new MediatorLiveData<>(); result.addSource(source, new Observer<X>() { @Override public void onChanged(@Nullable X x) { result.setValue(mapFunction.apply(x)); } }); return result; }
switchMap
devuelve un nuevo objeto LiveData
en lugar de un valor, es decir, cambia el real LiveData
por uno nuevo. Este método es análogo al operador Rx(io.reactivex.Observable)
switchMap. switchMapFunction
se ejecutará en el hilo main
.
Aquí hay una clase de ejemplo que contiene un nombre escrito de un usuario que es cadena de un EditTex
es un MutableLiveData
y devuelve a que LiveData
contiene una lista de objetos de usuario para usuarios que tienen el mismo nombre. Completa eso LiveData
volviendo a consultar un objeto de patrón de repositorio cada vez que cambia el nombre escrito.
class UserViewModel extends AndroidViewModel { MutableLiveData<String> nameQueryLiveData = ... LiveData<List<String>> getUsersWithNameLiveData() { return Transformations.switchMap( nameQueryLiveData, name -> myDataSource.getUsersWithNameLiveData(name)); } void setNameQuery(String name) { this.nameQueryLiveData.setValue(name); } }
Según la documentación, el código switchMap
del método es el siguiente:
@MainThread public static <X, Y> LiveData<Y> switchMap( @NonNull LiveData<X> source, @NonNull final Function<X, LiveData<Y>> switchMapFunction) { final MediatorLiveData<Y> result = new MediatorLiveData<>(); result.addSource(source, new Observer<X>() { LiveData<Y> mSource; @Override public void onChanged(@Nullable X x) { LiveData<Y> newLiveData = switchMapFunction.apply(x); if (mSource == newLiveData) { return; } if (mSource != null) { result.removeSource(mSource); } mSource = newLiveData; if (mSource != null) { result.addSource(mSource, new Observer<Y>() { @Override public void onChanged(@Nullable Y y) { result.setValue(y); } }); } } }); return result; }
Por favor, dame tus sugerencias y comentarios.
Espero que te sea de utilidad. Gracias por leer este post.
Añadir comentario