LiveData在开始观察时可以防止接收到最后一个值。

57

当开始观察时,是否可以防止LiveData接收最后一个值?

我考虑使用LiveData作为事件。

例如显示消息、导航事件或对话框触发等事件,类似于EventBus

ViewModel和片段之间的通信相关的问题,Google提供了LiveData来更新视图数据,但是当我们仅需要使用单个事件更新视图时,这种类型的通信就不合适了,而且我们不能在ViewModel中持有视图引用并调用某些方法,因为这会导致内存泄漏。

我发现了一些类似的东西SingleLiveEvent - 但它只能用于1个观察者而不是多个观察者。

--- 更新信息 ----

如@EpicPandaForce 所说 "没有理由将LiveData用作其不是的内容",可能问题的意图是MVVM中视图和ViewModel之间使用LiveData进行通信


1
我一直在苦苦挣扎着同样的问题,但是无论如何都找不到好的信息。看到其他人也遇到了这个问题,我真的很高兴。无论如何,我可能已经想出了一个相当干净的解决方案。准备好后,我会将其发布为答案。 - d4vidi
@d4vidi 希望看到解决方案。 - Pavel Poley
@PavelPoley 不好意思,我需要离线几天。我会尽快发布一些内容。 - d4vidi
@PavelPoley 面临相同的问题,那么你从以下解决方案中选择了哪一个?你有找到更好的解决方案吗? - Siddhpura Amit
1
我成功使用了https://github.com/hadilq/LiveEvent库。 - Michal Vician
显示剩余2条评论
17个回答

20

我正在使用来自Google示例的EventWraper类,在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;
    }
}

在 ViewModel 中:

 /** expose Save LiveData Event */
 public void newSaveEvent() {
    saveEvent.setValue(new Event<>(true));
 }

 private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();

 public LiveData<Event<Boolean>> onSaveEvent() {
    return saveEvent;
 }

在Activity/Fragment中

mViewModel
    .onSaveEvent()
    .observe(
        getViewLifecycleOwner(),
        booleanEvent -> {
          if (booleanEvent != null)
            final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
            if (shouldSave != null && shouldSave) saveData();
          }
        });

9
面对同样的问题,我创建了一些简单的 Kotlin 扩展函数,可以轻松解决这个问题。
使用方法如下:
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)

下面解释了基本的源代码。

如果需要更多细节(例如支持从Transformations.map返回的MediatorLiveData一类的特殊情况),可以在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()
    }
}


由于我们需要访问LiveData的可见方法getVersion()进行比较,因此在android.arch.lifecycle或androidx.lifecycle(AndroidX)包中创建一个类:
LiveDataHiddenApi.kt
package androidx.lifecycle

fun LiveData<*>.version(): Int {
    return this.getVersion()
}

9
有一些 RxJava 的经验,我习惯于认为这样的行为要求通常是 Observeable(在我们的情况下是 LiveData)的关注点。有许多操作符,比如replay(),可以控制实际发出的内容(以及何时发出),与用户实际发布的内容相比。本质上,SingleLiveEvent 也具有相同的概念。
因此,我提出了这个修改版的 MutableLiveData,名为 VolatileLiveData
open class VolatileLiveData<T> : MutableLiveData<T>() {
    private val lastValueSeq = AtomicInteger(0)
    private val wrappers = HashMap<Observer<in T>, Observer<T>>()

    @MainThread
    public override fun setValue(value: T) {
        lastValueSeq.incrementAndGet()
        super.setValue(value)
    }

    @MainThread
    public override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        val observerWrapper = ObserverWrapper(lastValueSeq, observer)
        wrappers[observer] = observerWrapper
        super.observe(owner, observerWrapper)
    }

    @MainThread
    public override fun observeForever(observer: Observer<in T>) {
        val observerWrapper = ObserverWrapper(lastValueSeq, observer)
        wrappers[observer] = observerWrapper
        super.observeForever(observerWrapper)
    }

    @MainThread
    public override fun removeObserver(observer: Observer<in T>) {
        val observerWrapper = wrappers[observer]
        observerWrapper?.let {
            wrappers.remove(observerWrapper)
            super.removeObserver(observerWrapper)
        }
    }
}

private class ObserverWrapper<T>(private var currentSeq: AtomicInteger, private val observer: Observer<in T>) : Observer<T> {
    private val initialSeq = currentSeq.get()
    private var _observer: Observer<in T> = Observer {
        if (currentSeq.get() != initialSeq) {
            // Optimization: this wrapper implementation is only needed in the beginning.
            // Once a valid call is made (i.e. with a different concurrent sequence), we
            // get rid of it any apply the real implementation as a direct callthrough.
            _observer = observer
            _observer.onChanged(it)
        }
    }

    override fun onChanged(value: T) {
        _observer.onChanged(value)
    }
}

首先,类似于 @emandt,我为每个实时值关联了唯一的序列,但严格限定在实时数据本身的范围内。每当将值设置为实时数据时,就会设置此序列。

其次,受 SingleLiveData 的启发,我引入了包装器来包装用户的观察者,仅在序列不同(即自订阅以来已经设置了一个值)时才调用它。

基本上就是这样,但有关完整文档,请转到我的gist

用法

至于使用 - 如果您完全控制 LiveData,只需像使用 MutableLiveData 一样使用 VolatileLiveData 即可。如果数据最初来自其他地方(例如 Room),则可以使用 Transformations.switchMap() 进行“切换”,以使用 volatile 实现。


你永远不会删除观察者。removeObserver 方法是从父类提供的私有 ObserverWrapper 类型中调用的。你需要解包它。 - Olsi Saqe

6

如果您正在使用原始的LiveData,我不认为可以防止它在开始观察时接收到最后一个值。您可以做的是扩展ViewModel,并使其仅在添加观察者时通知视图。

另一个选择是简单地忽略回调

  1. Add a flag to the ViewModel.

    private boolean isFirstTime = true;
    
    public boolean isFirstTime() { return isFirstTime; }
    
    public boolean onObserverAdded() { isFirstTime = false; }`
    
  2. Add checking in the callback

    @Override
    public void onChanged(@Nullable final String newName) {
    boolean ignore = ((MyViewModel)ViewModelProviders.of(MyActivity.this).get(MyViewModel.class)).isFirstTime();
    if(ignore) return;
    
    // Update the UI
    }
    
  3. Finally call onObserverAdded() after observer is added.


5
我创建了一个新类,它将保存我的真实数据和一个“特殊ID”:
class LiveDataItem {
    long mRealtimeNanos;
    YOUR_PREVIOUS_LIVEDATA_TYPE mData;
    LiveDataItem(YOUR_PREVIOUS_LIVEDATA_TYPE data, long realtimeNanos) {
        this.mRealtimeNanos = realtimeNanos;
        this.mData = data;
    }
}

然后我创建了一个新的“全局”变量:
final List<Long> mExcludedRealtimeNanos = new ArrayList<>;

在这里,我选择通过一个新的自定义“postValue()”方法,将我的“LiveDataItem”类型的“set/postValue()”设置/发布,而不是原始的“YOUR_PREVIOUS_LIVEDATA_TYPE”类型:

public void myPostValue(YOUR_PREVIOUS_LIVEDATA_TYPE data, boolean notifyWhenObserved) {
    long cRealtimeNanos = SystemClock.realtimeNanos();
    if (!notifyWhenObserved) mExcludedRealtimeNanos.add(cRealtimeNanos);
    ....postValue(new LiveDataItem(data, cRealtimeNanos));
}

接下来我创建了一个普通的观察者,它将收到所有"Changed()"事件,并在其中加入了关于"RealtimeNanos"的检查:

public void onChanged(LiveDataItem myDataItem) {
    boolean cFound = false;
    for (Long cRealtimeNanos : mExcludedRealtimeNanos) {
        if (cRealtimeNanos == myDataItem.mRealtimeNanos) {
            cFound = true;
            break;
        }
    }
    //check if it was found --> NO: it means that I wish to get the notification
    if (!cFound) mMyOnChangedCallback(myDataItem.mData)
}

显然,“mMyOnChangedCallback()”方法是回调函数,每当原始的“onChanged()”事件被触发时,该函数都会被调用,但仅在数据创建期间设置通知时才会这样做。您可以通过从“mExcludedRealtimeNanos”中删除“THAT RealtimeNanos”,然后将新的Observer附加到LiveData上,选择再次接收通知。 这段代码可以进行一些改进,但我只记得我以前写的内容(我现在不在电脑旁)。例如,我们可以在使用自定义的postValue()方法发布新数据时决定从“mExcludedRealtimeNanos”中删除一个值...

是的,我考虑给每个事件分配唯一的ID,这是个好主意,我会检查一下,谢谢。 - Pavel Poley

4

根据 jurij-pitulja 的回答,如果我们使用 kotlin协程,解决方案如下。

class Event<T>(private val content: T) {

    var isHandled = false
    private set

    fun getContentIfNotHandled(): T? {
        return takeIf { !isHandled }?.let {
            isHandled = true
            content
        }
    }
}

view model 类中,将 Flow.asLiveData() 替换为 emit new Event

val authResult: LiveData<Event<Result<AuthResponse>>> = _emailLiveData.switchMap { email ->
    liveData{
        repository.authRequest(email).collect{
            emit(Event(it))
        }
    }
}

实现在 fragment 中的 observer 方法
viewModel.authResult.observe(viewLifecycleOwner){
            it.getContentIfNotHandled()?.run {
                onAuthRequestComplete(this)
            }
        }

3
假设您正在观察具有生命周期的活动、片段或其他视图的实时数据,一个简单的方法是在活动或片段尚未可见/恢复时不执行任何操作来忽略初始的“最后值”,以便观察者得到通知。

MainActivity.kt

myviewmodel.myLiveData.observe(this) {
    if (lifecycle.currentState != Lifecycle.State.RESUMED) {
        return@observe
    }
}

或者,如果从片段观察一个虚拟机:

myviewmodel.myLiveData.observe(viewLifecycleOwner) {
    if (viewLifecycleOwner.lifecycle.currentState != Lifecycle.State.RESUMED) {
        return@observe
    }
}

任何观察实时数据的生命周期组件都可以每次接收更新时执行此检查。如果该组件当前不可见于屏幕上,我们会将更新丢弃并不采取任何措施。一旦我们观察到该组件仍将从VM接收其最后的值数据,但尚未可见/恢复,因此我们只需忽略它。


1
非常感谢您。我在这个问题上浪费了很多时间,最终找到了一些解决方案。棒极了! - Nayan

3
我创建了一个LiveData对象FreshLiveData,它只在调用setValuepostValue后才向观察者发送onChange。 < p> 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)
                }
            }
        }

    }
}

这里有一个扩展,可以将现有的 LiveData 转换为 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
}

使用方法:

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

2

我也有同样的需求。我通过扩展MutableLiveData来实现了这一点。

“Original Answer”翻译成中文是“最初的回答”。

package com.idroidz.android.ion.util;    
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;

import java.util.concurrent.atomic.AtomicBoolean;

public class VolatileMutableLiveData<T> extends MutableLiveData<T> {


    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @MainThread
    public void observe(LifecycleOwner owner, final Observer<T> observer) {
        // Observe the internal MutableLiveData
        mPending.set(false);
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                if (mPending.get()) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        mPending.set(true);
        super.setValue(t);
    }

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

    public void callFromThread() {
        super.postValue(null);
    }
}

在后台的观察者在返回前台后不会得到更新的值,所以这种方法行不通。 - Egor Neliuba
@EgorNeliuba 你介意解释一下为什么这不起作用吗?难道不是防止观察者在回到前台后被触发,这正是我们要解决的问题吗? - BabyishTank

1

我写了一个小型的实用类,可以快速地阻止任何代码块的第一次运行,像这样:

class SkipFirstRun {
    var canRun = false
    fun run(codeToRunButNotFirstTime: () -> Unit) {
        if (canRun) codeToRunButNotFirstTime()
        canRun = true
    }
}

在我的 Fragment 中,我只需声明它的一个实例变量:
class TrainingDetailCardInfoFragment 
    override val viewModel: TrainingViewModel by viewModels 
    private var skipInitialEndOfTraining = SkipFirstRun()
...

然后像这样使用它

    viewModel.runningTask.observe(viewLifecycleOwner) { task ->
            skipInitialEndOfTraining.run {
                Log.debug("Playing stopped!")
                sound.play(requireContext(), R.raw.endoftraining)
                playBarViewModel.stop()
            }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接