LiveData with single events

Hadi Lashkari Ghouchani
ProAndroidDev
Published in
5 min readOct 19, 2018

--

You may searched for SingleLiveEvent in the Internet to find a good solution for a LiveData that sends events once. There are multiple sources like “LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)” written by Jose Alcérreca and “SingleLiveEvent (LiveData) with multi observers” written by Kenji Abe. This article is following their steps, so you should read them first.

The problem

The problem is started since LiveData promises some advantages where you can find them in its documentation. I list them here by the way:

Ensures your UI matches your data state LiveData follows the observer pattern. LiveData notifies Observer objects when the lifecycle state changes. You can consolidate your code to update the UI in these Observer objects. Instead of updating the UI every time the app data changes, your observer can update the UI every time there's a change.

No memory leaks Observers are bound to Lifecycle objects and clean up after themselves when their associated lifecycle is destroyed.

No crashes due to stopped activities If the observer's lifecycle is inactive, such as in the case of an activity in the back stack, then it doesn’t receive any LiveData events.

No more manual lifecycle handling UI components just observe relevant data and don’t stop or resume observation. LiveData automatically manages all of this since it’s aware of the relevant lifecycle status changes while observing.

Always up to date data If a lifecycle becomes inactive, it receives the latest data upon becoming active again. For example, an activity that was in the background receives the latest data right after it returns to the foreground.

Proper configuration changes If an activity or fragment is recreated due to a configuration change, like device rotation, it immediately receives the latest available data.

Sharing resources You can extend a LiveData object using the singleton pattern to wrap system services so that they can be shared in your app. The LiveData object connects to the system service once, and then any observer that needs the resource can just watch the LiveData object. For more information, see Extend LiveData.

But some of these advantages cannot be useful in all scenarios and there is no way to disable them when you instantiate a LiveData. For instance, the property of “always up to date data” cannot be disabled and the main problem which this article wants to tackle is a way to disabling it.

However, I have to thank Google for the “proper configuration changes” property, which is so so useful. But still we need to be able to disable it when we want. I have no scenario where I need to disable it but please let people choose.

The suggested ways to solve the problem

After reading the article of Jose you will find it’s github source of the main class of his recommended solution here.

But someone named feinstein asked two valid questions in that page.

  1. Jose’s solution has lack of supporting multiple observers, which is one of promises of LiveData by name of “Sharing resources”.
  2. It’s not thread-safe.

And I can add one more problem. As here by using LiveData we want the advantage of using functional programming in our code and one of principles of functional programming is using of immutable data structures. This principle would be breaking with the recommended solution of Jose.

After Jose, Kenji tries to solve the problem of “Sharing resources”.

But as you can see the internalObserver is passed once to the super.observe method so it observed once with the first owner and all other owners is discarded and wrong behavior starts here. The other bad behavior of this class is that the removeObserver is not working as expected, because, on removeObserver method the instance of internalObserver will get back, which is not in the set. So nothing will be removed from the set.

The recommended solution

You can find the standard approach to deal with multiple observers in the LiveData class itself, which is to wrap up original observers. As the LiveData class doesn’t allow us to access its ObserverWrapper class we have to create our version.

ATTENTION: PLEASE LOOK AT THE SECOND UPDATE SECTION

First of all this class is thread-safe because observers property is final and CopyOnWriteArraySet is thread-safe too. Second, every observers register to the parent LiveData with their own owner. Third, in the removeObserver method we expect a ObserverWrapper which we already registered in observe method and we have it in observers set to remove. All these imply we support “Sharing resources” property correctly.

Update 11/2018

As one of members of my team mentioned, I forgot to handle the owner: LifecycleOwner in removeObservers method! This can be a problem if in a page of your app you have multiple Fragments as LifecycleOwners with one ViewModel. Let me correct my solution to this:

Additional to the previous arguments, this is also thread-safe because ConcurrentHashMap is thread-safe.

Tip

Here, it’s proper to add a tip. you can define the following extension in your code.

Then in case of wanting to have a single event, just call this extension method in your ViewModel like this:

And you can use this singleLiveEvent like other LiveDatas.

Update 02/2019

As Jeffrey McNally-Dawes correctly pointed out in Responses section, there was a bug in my previous solution!! I noticed that one of my assumptions was wrong so I reached to the wrong solution! To solve it forever, I created a library where there’s enough tests to show my assumptions and verify them. You can find the library at https://github.com/hadilq/LiveEvent and the LiveEvent class here. In case of any other problem you can just make a pull request to show that I’m wrong and where. Also instead of copy/paste the LiveEvent class, you can import the library via Maven, so any bug fix will be in your project as soon as updating version of the library.

Update 01/2020

There is another solution for this problem where you can use PublishProcessor instead of LiveEvent and use my new article https://medium.com/@hadilq.dev/rxjava-instead-of-livedata-in-mvvm-f95e8fe0aa41 to observe it.

In the end, I’ll be more than happy to read your feedback about it.

--

--