Consumable LiveData Wrapper
Apr 12, 2021
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.