LiveData es una gran herramienta para vincular el estado de sus datos y objetos con un ciclo de vida (LifecycleOwner, generalmente un Fragmento o Actividad).
Por lo general, LiveData se coloca en ViewModel y se usa para actualizar el estado de su IU. A menudo, un ViewModel puede sobrevivir a un LifecycleOwner y mantener un estado LiveData. Dicho mecanismo es adecuado cuando necesita guardar datos y restaurarlos después de un tiempo, por ejemplo, después de un cambio de configuración.
Pero, ¿qué pasa si queremos usar el mecanismo de los eventos, no los estados? Y es necesario en el contexto del ciclo de vida del navegador (LifecycleOwner). Por ejemplo, necesitamos mostrar un mensaje después de una operación asincrónica, siempre que LifecycleOwner siga vivo, tenga navegadores activos y esté listo para actualizar su IU. Si usamos LiveData, recibiremos el mismo mensaje después de cada cambio de configuración, o con cada nuevo suscriptor. Una de las soluciones que se sugiere, después de procesar los datos en algún navegador, es restablecer estos datos a LiveData.
Por ejemplo, un código como este:
Observer { handle(it) yourViewModel.liveData.value = null }
Pero este enfoque tiene varias desventajas y no cumple con todos los requisitos necesarios.
Me gustaría tener un mecanismo de eventos que:
- notifica solo a suscriptores activos
- en el momento de la suscripción no notifica sobre datos anteriores,
- tiene la capacidad de establecer el indicador manejado en verdadero para interrumpir el procesamiento posterior del evento.
Implementé la clase MutableLiveEvent, que tiene todas las propiedades anteriores y que puede funcionar como un LiveData normal.
Cómo utilizar:
Todo el código está disponible en
GitHub , y a continuación hablaré un poco sobre la implementación.
class MutableLiveEvent<T : EventArgs<Any>> : MutableLiveData<T>() { internal val observers = ArraySet<PendingObserver<in T>>() @MainThread override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { val wrapper = PendingObserver(observer) observers.add(wrapper) super.observe(owner, wrapper) } override fun observeForever(observer: Observer<in T>) { val wrapper = PendingObserver(observer) observers.add(wrapper) super.observeForever(observer) } @MainThread override fun removeObserver(observer: Observer<in T>) { when (observer) { is PendingObserver -> { observers.remove(observer) super.removeObserver(observer) } else -> { val pendingObserver = observers.firstOrNull { it.wrappedObserver == observer } if (pendingObserver != null) { observers.remove(pendingObserver) super.removeObserver(pendingObserver) } } } } @MainThread override fun setValue(event: T?) { observers.forEach { it.awaitValue() } super.setValue(event) } }
La idea es que dentro de la clase MutableLiveEvent, en los métodos observe y observeForever, envuelva los navegadores en una clase interna especial PendingObserver, que llama al navegador real solo una vez y solo si el indicador pendiente se establece en verdadero y el evento aún no se ha procesado.
internal class PendingObserver<T : EventArgs<Any>>(val wrappedObserver: Observer<in T>) : Observer<T> { private var pending = false override fun onChanged(event: T?) { if (pending && event?.handled != true) { pending = false wrappedObserver.onChanged(event) } } fun awaitValue() { pending = true } }
En PendingObserver, el indicador pendiente se establece en falso de forma predeterminada. Esto resuelve el ítem 2 (no notificar los datos antiguos) de nuestra lista.
Y el código en MutableLiveEvent
override fun setValue(event: T?) { observers.forEach { it.awaitValue() } super.setValue(event) }
Primero establece pendiente a verdadero y solo luego actualiza los datos dentro de sí mismo. Esto asegura la implementación de la reivindicación 1. (alerta solo a suscriptores activos).
El último punto del que aún no he hablado es EventArgs. Esta clase es una generalización en la que hay un indicador manejado para interrumpir el procesamiento posterior del evento (Cláusula 3).
open class EventArgs<out T>(private val content: T?) { var handled: Boolean = false val data: T? get() { return if (handled) { null } else { content } } }
Eso es todo, ¡gracias por mirar!