Consumable LiveData Wrapper

Apr 12, 2021

IMG_1253.jpg

If you have used ViewModels with LiveData, chances are you ran into an issue like this:

You try loading some data in ViewModel and it fails. You then notify your Fragment about the failure using LiveData. The Fragment shows an error Snackbar and all is well…until you rotate your device and see the error Snackbar again. The problem is the ViewModel survives orientation change (by design) and when Fragment’s view is recreated, the same LiveData is observed again causing the previous error to be “replayed”.


From the early days of LiveData, some of us have been using a SingleLiveEvent.

Recently, I came across a more Kotlin alternative to SingleLiveData posted by aminography here:

import java.util.concurrent.atomic.AtomicBoolean

class OneTimeEvent<T>(
  private val value: T
) {

  private val isConsumed = AtomicBoolean(false)

  private fun getValue(): T? =
    if (isConsumed.compareAndSet(false, true)) value else null

  fun consume(block: (T) -> Unit): T? =
    getValue()?.also(block)
}

fun <T> T.toOneTimeEvent() = OneTimeEvent(this)

Then, in your ViewModel:

private val _isError = MutableLiveData<OneTimeEvent<Boolean>>()
val isError: LiveData<OneTimeEvent<Boolean>> = _isError

And to post to that LiveData, you’d do:

_isError.value = OneTimeEvent(someValue)

Or using the extension function:

_isError.value = someValue.toOneTimeEvent()

And finally in your Fragment:

viewModel.isError.observe(viewLifecycleOwner) {
  it.consume {
    // use the value
  }
}

Once consumed, the value will not emit again.

 
Previous
Previous

Jetpack Navigation: Bottom Nav and Multiple Backstacks

Next
Next

RecyclerView ConcatAdapter