Android LiveData Events

LiveData is a great tool for linking the state of your data and objects with a life cycle (LifecycleOwner, usually a Fragment or Activity).



Typically, LiveData is placed in the ViewModel and used to update the state of your UI. Often, a ViewModel can survive a LifecycleOwner and maintain a LiveData state. Such a mechanism is suitable when you need to save data and restore it after a while, for example, after a configuration change.



But what if we want to use the mechanism of events, not states? And it is necessary in the context of the browser life cycle (LifecycleOwner). For example, we need to display a message after an asynchronous operation, provided that LifecycleOwner is still alive, has active browsers, and is ready to update its UI. If we use LiveData, then we will receive the same message after each configuration change, or with each new subscriber. One of the solutions that suggests itself is to process the data in LiveData after processing the data in some browser.



For example, a code like this:



Observer { handle(it) yourViewModel.liveData.value = null }
      
      





But this approach has several disadvantages and does not meet all the necessary requirements.



I would like to have an event mechanism that:



  1. notifies only active subscribers
  2. at the time of subscription does not notify about previous data,
  3. has the ability to set the handled flag to true to interrupt further processing of the event.


I implemented the MutableLiveEvent class, which has all of the above properties and which can work like a regular LiveData.



How to use:



 //   EventArgs        class MyIntEventArgs(data: Int) : EventArgs<Int>(data) //  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 })
      
      





All code is available on GitHub , and below I will talk a little about the implementation.



 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) } }
      
      





The idea is that inside the MutableLiveEvent class, in the observe and observeForever methods, wrap browsers in a special internal class PendingObserver, which calls the real browser only once and only if the pending flag is set to true and the event has not yet been processed.



 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 } }
      
      





In PendingObserver, the pending flag is set to false by default. This solves item 2 (do not notify of old data) from our list.



And the code in MutableLiveEvent



 override fun setValue(event: T?) { observers.forEach { it.awaitValue() } super.setValue(event) }
      
      





First sets pending to true, and only then updates the data within itself. This ensures the implementation of claim 1. (alert only active subscribers).



The last point that I have not talked about yet is EventArgs. This class is a generalization in which there is a handled flag to interrupt further processing of the event (Clause 3).



 open class EventArgs<out T>(private val content: T?) { var handled: Boolean = false val data: T? get() { return if (handled) { null } else { content } } }
      
      





That's all, thanks for watching!



All Articles