Is it possible to prevent LiveData
receive the last value when start observing?
I am considering to use LiveData
as events.
For example eve
According to answer of jurij-pitulja.
if we are using kotlin coroutines
the solution look likes this.
class Event<T>(private val content: T) {
var isHandled = false
private set
fun getContentIfNotHandled(): T? {
return takeIf { !isHandled }?.let {
isHandled = true
content
}
}
}
Inside of view model
class replacing Flow.asLiveData()
into emit new Event
val authResult: LiveData<Event<Result<AuthResponse>>> = _emailLiveData.switchMap { email ->
liveData{
repository.authRequest(email).collect{
emit(Event(it))
}
}
}
Implementing observer
method inside of fragment
viewModel.authResult.observe(viewLifecycleOwner){
it.getContentIfNotHandled()?.run {
onAuthRequestComplete(this)
}
}
More simple solution would be to use EventLiveData lib :
implementation 'com.rugovit.eventlivedata:eventlivedata:1.0'
MutableEventLiveData<String> eventLiveData =new MutableEventLiveData<>();
viewModel.event.observe(this, Observer {
// ...
})
You use it just like regular live data. It is extension of livedata and supports every feature of livedata. Unlike other solutions this supports multiple observers.
Github link: https://github.com/rugovit/EventLiveData
Faced the same problem, and I created some simple kotlin extention functions which can solve the problem easily.
Usage as below:
val liveData = MutableLiveData<String>()
liveData.value = "Hello"
val freshResult = mutableListOf<String>()
val normalResult = mutableListOf<String>()
liveData.observeForeverFreshly(Observer {
freshResult.add(it)
})
liveData.observeForever(Observer {
normalResult.add(it)
})
liveData.value = "World"
assertEquals(listOf("World"), freshResult)
assertEquals(listOf("Hello", "World"), normalResult)
Basic source code is explained as bllow.
For some more detail (to support some special situations for example MediatorLiveData
returned from Transformations.map), you can view it in github : livedata-ext
FreshLiveData.kt
fun <T> LiveData<T>.observeFreshly(owner: LifecycleOwner, observer: Observer<in T>) {
// extention fuction to get LiveData's version, will explain in below.
val sinceVersion = this.version()
this.observe(owner, FreshObserver<T>(observer, this, sinceVersion))
}
fun <T> LiveData<T>.observeForeverFreshly(observer: Observer<in T>, skipPendingValue: Boolean = true) {
val sinceVersion = this.version()
this.observeForever(FreshObserver<T>(observer, this, sinceVersion))
}
// Removes the observer which has been previously observed by [observeFreshly] or [observeForeverFreshly].
fun <T> LiveData<T>.removeObserverFreshly(observer: Observer<in T>) {
this.removeObserver(FreshObserver<T>(observer, this, 0))
}
class FreshObserver<T>(
private val delegate: Observer<in T>,
private val liveData: LiveData<*>,
private val sinceVersion: Int
) : Observer<T> {
override fun onChanged(t: T) {
if (liveData.version() > sinceVersion) {
delegate.onChanged(t)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
if (delegate != (other as FreshObserver<*>).delegate) return false
return true
}
override fun hashCode(): Int {
return delegate.hashCode()
}
}
Becasue we need to access LiveData's pcakage visibile methond getVersion()
for comparasion, so create a class in package android.arch.lifecycle
or androidx.lifecycle
(AndroidX):
LiveDataHiddenApi.kt
package androidx.lifecycle
fun LiveData<*>.version(): Int {
return this.getVersion()
}
I created a LiveData object FreshLiveData
, which emits the onChange
to the observer only after there is a call to setValue
or postValue
.
FreshLiveData.kt
/**
* A lifecycle-aware observable that emits only new data after subscription. Any data that has
* already been set, before the observable has subscribed, will be ignored.
*
* This avoids a common problem with events: on configuration change (like rotation, font change) an
* update can be emitted if the observer is active. This LiveData only calls the observable if
* there's an explicit call to setValue() or postValue().
*
* All observers will be notified of change(s).
*/
class FreshLiveData<T> : MutableLiveData<T>() {
private val observers = mutableMapOf<LifecycleOwner, FreshLiveDataObserver>()
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
@Suppress("UNCHECKED_CAST")
observer as Observer<T>
observers[owner].apply {
if (this == null) {
observers[owner] = FreshLiveDataObserver(observer).apply {
super.observe(owner, this)
}
} else {
add(observer)
}
}
}
override fun observeForever(observer: Observer<in T>) {
@Suppress("UNCHECKED_CAST")
observer as Observer<T>
observers[ProcessLifecycleOwner.get()].apply {
if (this == null) {
observers[ProcessLifecycleOwner.get()] = FreshLiveDataObserver(observer).apply {
super.observeForever(this)
}
} else {
add(observer)
}
}
}
override fun removeObservers(owner: LifecycleOwner) {
observers.remove(owner)
super.removeObservers(owner)
}
override fun removeObserver(observer: Observer<in T>) {
@Suppress("UNCHECKED_CAST")
observers.forEach { it.value.remove(observer as Observer<T>) }
super.removeObserver(observer)
}
@MainThread
override fun setValue(t: T?) {
observers.forEach { it.value.setPending() }
super.setValue(t)
}
override fun postValue(value: T) {
observers.forEach { it.value.setPending() }
super.postValue(value)
}
inner class FreshLiveDataObserver(observer: Observer<T>) : Observer<T> {
private val observers = mutableSetOf<Observer<T>>()
private val pending = AtomicBoolean(false)
init {
observers.add(observer)
}
fun add(observer: Observer<T>) = observers.add(observer)
fun remove(observer: Observer<T>) = observers.remove(observer)
fun setPending() = pending.set(true)
override fun onChanged(t: T) {
if (pending.compareAndSet(true, false)) {
observers.forEach { observer ->
observer.onChanged(t)
}
}
}
}
}
and here is an extension for transforming an existing LiveData
to a FreshLiveData
.
LiveDataExtensions.kt
@MainThread
fun <T> LiveData<T>.toFreshLiveData(): LiveData<T> {
val freshLiveData = FreshLiveData<T>()
val output = MediatorLiveData<T>()
// push any onChange from the LiveData to the FreshLiveData
output.addSource(this) { liveDataValue -> freshLiveData.value = liveDataValue }
// then push any onChange from the FreshLiveData out
output.addSource(freshLiveData) { freshLiveDataValue -> output.value = freshLiveDataValue }
return output
}
Usage:
val liveData = MutableLiveData<Boolean>()
liveData.value = false
liveData.toFreshLiveData().observeForever {
// won't get called with `it = false` because the observe was setup after setting that livedata value
// will get called with `it = true` because the observer was setup before setting that livedata value
}
liveData.value = false
val freshLiveData = FreshLiveData<Boolean>()
freshLiveData.value = false
freshLiveData.observeForever {
// won't get called with `it = false` because the observe was setup after setting that livedata value
// will get called with `it = true` because the observer was setup before setting that livedata value
}
freshLiveData.value = true
I`m using this EventWraper class from Google Samples inside MutableLiveData
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
public class Event<T> {
private T mContent;
private boolean hasBeenHandled = false;
public Event( T content) {
if (content == null) {
throw new IllegalArgumentException("null values in Event are not allowed.");
}
mContent = content;
}
@Nullable
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return mContent;
}
}
public boolean hasBeenHandled() {
return hasBeenHandled;
}
}
In ViewModel :
/** expose Save LiveData Event */
public void newSaveEvent() {
saveEvent.setValue(new Event<>(true));
}
private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();
LiveData<Event<Boolean>> onSaveEvent() {
return saveEvent;
}
In Activity/Fragment
mViewModel
.onSaveEvent()
.observe(
getViewLifecycleOwner(),
booleanEvent -> {
if (booleanEvent != null)
final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
if (shouldSave != null && shouldSave) saveData();
}
});
Just ignore data before android.arch.lifecycle.LiveData#observe
function called.
class IgnoreHistoryLiveData<T> : MutableLiveData<T>() {
private val unactivedObservers = LinkedBlockingQueue<WrapperObserver<T>>()
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
val wo = WrapperObserver<T>(observer)
unactivedObservers.add(wo)
super.observe(owner, wo)
}
override fun setValue(value: T) {
while (unactivedObservers.isNotEmpty()) {
unactivedObservers.poll()?.actived = true
}
super.setValue(value)
}
}
private class WrapperObserver<T>(private val origin: Observer<T>) : Observer<T> {
var actived = false
override fun onChanged(t: T?) {
if (actived) {
origin.onChanged(t)
}
}
}