O LiveData é uma ótima ferramenta para vincular o estado de seus dados e objetos a um ciclo de vida (LifecycleOwner, geralmente um Fragmento ou Atividade).
Normalmente, o LiveData é colocado no ViewModel e usado para atualizar o estado da sua interface do usuário. Freqüentemente, um ViewModel pode sobreviver a um LifecycleOwner e manter um estado LiveData. Esse mecanismo é adequado quando você precisa salvar dados e restaurá-los após algum tempo, por exemplo, após uma alteração na configuração.
Mas e se quisermos usar o mecanismo de eventos, não estados? E é necessário no contexto do ciclo de vida do navegador (LifecycleOwner). Por exemplo, precisamos exibir uma mensagem após uma operação assíncrona, desde que o LifecycleOwner ainda esteja ativo, tenha navegadores ativos e esteja pronto para atualizar sua interface do usuário. Se usarmos o LiveData, receberemos a mesma mensagem após cada alteração na configuração ou com cada novo assinante. Uma das soluções que se sugere, após o processamento dos dados em algum navegador, é redefinir esses dados para o LiveData.
Por exemplo, um código como este:
Observer { handle(it) yourViewModel.liveData.value = null }
Mas essa abordagem tem várias desvantagens e não atende a todos os requisitos necessários.
Eu gostaria de ter um mecanismo de evento que:
- notifica apenas assinantes ativos
- no momento da assinatura não notifica sobre dados anteriores,
- tem a capacidade de definir o sinalizador manipulado como true para interromper o processamento adicional do evento.
Eu implementei a classe MutableLiveEvent, que possui todas as propriedades acima e que podem funcionar como um LiveData normal.
Como usar:
Todo o código está disponível no
GitHub , e abaixo vou falar um pouco sobre a implementação.
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) } }
A idéia é que dentro da classe MutableLiveEvent, nos métodos observe e observeForever, agrupe os navegadores em uma classe interna especial PendingObserver, que chama o navegador real apenas uma vez e somente se o sinalizador pendente estiver definido como verdadeiro e o evento ainda não tiver sido processado.
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 } }
No PendingObserver, o sinalizador pendente é definido como falso por padrão. Isso resolve o item 2 (não notificar dados antigos) da nossa lista.
E o código em MutableLiveEvent
override fun setValue(event: T?) { observers.forEach { it.awaitValue() } super.setValue(event) }
Primeiros conjuntos pendentes para true e somente depois atualizam os dados dentro de si. Isso garante a implementação da reivindicação 1. (alerta apenas assinantes ativos).
O último ponto sobre o qual ainda não falei é o EventArgs. Essa classe é uma generalização na qual há um sinalizador manipulado para interromper o processamento adicional do 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 } } }
Isso é tudo, obrigado por assistir!