События в Android на базе LiveData
LiveData – это отличный инструмент для связывания состояния ваших данных и объектов с жизненным циклом (LifecycleOwner, обычно это Fragment или Activity).
Обычно объекты LiveData помещаются во ViewModel и используются для обновления состояния вашего UI. Часто ViewModel может пережить пересоздание LifecycleOwner и сохранить состояние LiveData. Такой механизм подходит, когда вам нужно сохранить данные и восстановить их через некоторое время, например, после смены конфигурации.
Но что если мы хотим использовать механизм событий, а не состояний? Причем обязательно в контексте жизненного цикла обозревателя (LifecycleOwner). Например, нам нужно вывести сообщение после асинхронной операции при условии, что LifecycleOwner еще жив, имеет активных обозревателей (Observers) и готов обновить свой UI. Если мы будем использовать LiveData, то будем получать одно и то же сообщение после каждой смены конфигурации или при каждом новом подписчике. Одно из решений, которое напрашивается, это после обработки данных в некотором обозревателе обнулить эти данные в LiveData.
Например, такой код:
Observer { handle(it) yourViewModel.liveData.value = null }
Но такой подход имеет ряд недостатков и не отвечает всем необходимым требованиям.
Мне бы хотелось иметь механизм событий, который:
- Оповещает только активных подписчиков.
- В момент подписки не оповещает о предыдущих данных.
- Имеет возможность выставить флаг handled в true, чтобы прервать дальнейшую обработку события.
Я реализовал класс MutableLiveEvent, который обладает всеми вышеперечисленными свойствами и который может работать как обычный LiveData.
Как использовать:
1. Создайте свой экземпляр EventArgs для передачи своих типов данных в событии
class MyIntEventArgs(data: Int) : EventArgs<Int>(data)
2. Создайте обычную viewModel
class MainViewModel : ViewModel() { private val myEventMutable = MutableLiveEvent<MyIntEventArgs>() val myEvent = myEventMutable as LiveData<MyIntEventArgs> fun sendEvent(data: Int) { myEventMutable.value = MyIntEventArgs(data) } }
val vm = ViewModelProviders.of(this).get(MainViewModel::class.java) vm.myEvent.observe(this, Observer { //Здесь обработка события /* * Если событие обработано и вы не хотите, чтобы оно * дошло до других обозревателей, то укажите handled = true */ it.handled = true })
Весь код доступен на GitHub, а ниже я немного расскажу о реализации.
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) } }
Идея заключается в том, чтобы внутри класса MutableLiveEvent в методах observe и observeForever оборачивать обозреватели в специальный внутренний класс PendingObserver, который вызывает реальный обозреватель только один раз и только если выставлен флаг pending в true, а событие еще не обработано.
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 } }
В PendingObserver флаг pending выставлен в false по умолчанию. Это решает п.2 (не оповещать о старых данных) из нашего списка.
А код в MutableLiveEvent
override fun setValue(event: T?) { observers.forEach { it.awaitValue() } super.setValue(event) }
сначала выставляет pending в true и только потом обновляет данные внутри себя. Это обеспечивает выполнение п.1 (оповещение только активных подписчиков).
Последний момент, о котором я еще не рассказал, — это EventArgs. Это класс — обобщение, в котором есть флаг handled для прерывания дальнейшей обработки события (п.3).
open class EventArgs<out T>(private val content: T?) { var handled: Boolean = false val data: T? get() { return if (handled) null else content } }
На этом все, спасибо за внимание!
Источник: События на базе LiveData Android