ömer iyiöz
DigiGeek
Published in
4 min readFeb 27, 2021

--

Why does using LiveData is buggy in some cases and how to solve the problem using SingleLiveEvent ? Try with examples.

In this article, i got help from Jose Alcerreca’s great article, and implemented the cases he mentioned.

Example 1 :

In the first example we used MutableLiveData. When we clicked the textView, clickedLiveData.postValue(true) is called. Thus, the observer is invoked and 2nd activity is launched. Then if you press back button, 1st activity is opened. Then if you rotate the device, 2nd activity is opened. Why ? Because activity is recreated and observer is invoked again. This is a common bug. We should solve it.

To solve the bug in the first example, you can use a SingleLiveEvent or Event wrapper.

  • 2nd and 3rd examples show SingleLiveEvent usage.
  • 4th and 5th examples show Event wrapper usage.

1st, 2nd and 4th examples contains a simple textView. When you clicked the textView, liveData.postValue is called thus observer is invoked and next activity is launched. When you press back and rotate the device observer is invoked again in 1st example which causes a bug. In the 2nd and 4th examples, since observer is not invoked, there is no bug.

Example 2 :

In the 2nd example we used SingleLiveEvent<Boolean>. When we clicked the textView, clickedSingleLiveEvent.postValue(true) is called. Thus, the observer is invoked and 3rd activity is launched. Then if you press back button, 2nd activity is opened. Then if you rotate the device, you are still in the 2nd activity. Although the 2nd activity is recreated after rotation, observer is not invoked again. Thus 2nd activity does not contain the bug which exists in 1st activity.

Lets analyze SingleLiveEvent class. When you set a value for SingleLiveEvent, mPending becomes true. Then one of its observer sets mPending value to false and invoked. If there are multiple observers, the other observers are not invoked since mPending is false.

In other words, if we call setValue of SingleLiveEvent, only one observer is invoked. SingleLiveEvent observers don’t invoke again.

Example 3:

In the 3rd example we used SingleLiveEvent<String>. We observe SingleLiveEvent from three places(Activity3, Fragment3A, Fragment3B). When

_clickedSingleLiveEvent.value = itemId

is called. Only one observer is called as we said earlier. Other 2 observers are not invoked. If you rotate the device, no observer is invoked. But if you call

_clickedSingleLiveEvent.value = itemId

again, one observer is invoked again . The other 2 observers are still not invoked.

If you have multiple observers and you want multiple observers are invoked, please look at the 5th example.

class SingleLiveEvent<T> : MutableLiveData<T>() {

private val mPending = AtomicBoolean(false)

@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {

if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}

// Observe the internal MutableLiveData
super.observe(owner, object : Observer<T> {
override fun onChanged(t: T?) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
})
}

@MainThread
override fun setValue(@Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}

/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
setValue(null)
}

companion object {

private val TAG = "SingleLiveEvent"
}
}

Example 4:

When you clicked the textView,

_clickedEvent.value = Event(itemId)

is called. clickedEvent observer is invoked and next activity is launched. Open next activity and press back. When you rotate the device, 4th activity is still seen. There is no bug.

Event wrapper class

Example 5 :

In the 5th example we used

MutableLiveData<Event<String>>()

We observe this MutableLiveData from 3 places(Activity5, Fragment5A, Fragment5B). When we clicked textView, the following line

_clickedEvent.value = Event(itemId)

is called. All observes are invoked but only two textView value is changed. Here is the details:

  • In activity, when the observer is invoked, peekContent returns the current string value and textView value is updated.
  • In Fragment5A and Fragment5B, when the observer is invoked, getContentIfNotHandled() is called and hasBeenHandled is set to true and returns the content string. When the next getContentIfNotHandled() is called, it returns null since hasBeenHandled is true. Thus one textView value is updated and one textView value is not updated in Fragment5a and Fragment5b.

You can try the examples i mentioned in this article by cloning the following repo.

In this article, i explained why we need a SingleLiveEvent and Event wrapper class. If you liked it, please clap for support.

Happy coding!

--

--