让每个观察者仅在 subscribing/observing 时接收 *new* LiveData

Let every Observer only receive *new* LiveData upon subscribing/observing

每当您在 LiveData 上调用 .observe() 时,Observer 都会收到该 LiveData 的最后一个值。这在某些情况下可能有用,但在我的情况下没有用。

  1. 每当我调用 .observe() 时,我希望 Observer 只接收未来的 LiveData 更改,而不是调用 .observe() 时它持有的值。

  2. 对于一个 LiveData 实例,我可能有多个 Observer。我希望它们在发生时都能收到 LiveData 更新。

  3. 我希望每个观察者只使用一次 LiveData 更新。我认为这只是对第一个要求的重新措辞,但我的头脑已经在旋转了,我不确定。


在谷歌搜索这个问题时,我想到了两种常见的方法:

  1. 将数据包装在 LiveData<SingleEvent<Data>> 中,如果它已经被消耗,则检查此 SingleEvent class。

  2. 扩展MediatorLiveData,如果观察者已经得到事件,则使用查找图

可以在此处找到这些方法的示例: https://gist.github.com/JoseAlcerreca/5b661f1800e1e654f07cc54fe87441af#gistcomment-2783677 https://gist.github.com/hadilq/f095120348a6a14251a02aca329f1845#file-liveevent-kt https://gist.github.com/JoseAlcerreca/5b661f1800e1e654f07cc54fe87441af#file-event-kt

不幸的是,none 这些示例解决了 所有 我的要求。大多数时候,问题是任何新的观察者在订阅时仍然会收到最后的 LiveData 值。这意味着每当用户在屏幕之间导航时,已经显示的 Snackbar 会一次又一次地显示。


为了让您了解我在说什么/我在编码什么:

我正在关注 Android 架构组件的 LiveData MVVM 设计:

因此,当删除完成后,Repository 会通知 ViewModel,ViewModel 会通知 ListFragment。

现在,当用户切换到第二个 ListFragment 时,会发生以下情况:

下面是一些简化的代码:

片段:

viewModel.dataEvents.observe(viewLifecycleOwner, Observer { showSnackbar() })
viewModel.deleteEntry()

ViewModel:

val dataEvents: LiveData<EntryListEvent> = Transformations.switchMap(repository.events, ::handleRepoEvent)
fun deleteEntry() = repository.deleteEntry()
private fun handleRepoEvent(event: RepositoryEvent): LiveData<EntryListEvent> {
    // convert the repository event to an UI event
}

存储库:

private val _events = MutableLiveData<RepositoryEvent>()
val events: LiveData<RepositoryEvent>
    get() = _events

fun deleteEntry() {
    // delete it from database
    _events.postValue(RepositoryEvent.OnDeleteSuccess)
}

我的问题是这样解决的:

事件包装器 class 以保留事件相关数据(从 google 样本复制)

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;
    }
}

接下来,我创建事件观察器 class,它处理数据检查(空等):

public class EventObserver<T> implements Observer<Event<T>> {

  @Override
  public void onChanged(Event<T> tEvent) {
    if (tEvent != null && !tEvent.hasBeenHandled())
      onEvent(tEvent.getContentIfNotHandled());
  }

  protected void onEvent(@NonNull T content) {}
}

以及事件处理程序 class,以简化从视图模型的访问:

public class EventHandler<T> {

  private MutableLiveData<Event<T>> liveEvent = new MutableLiveData<>();

  public void observe(@NonNull LifecycleOwner owner, @NonNull EventObserver<T> observer){
      liveEvent.observe(owner, observer);
  }

    public void create(T content) {
    liveEvent.setValue(new Event<>(content));
  }
}

示例:

在ViewModel.class中:

private EventHandler<Boolean> swipeEventHandler = new EventHandler<>();

  public EventHandler<Boolean> getSwipeEventHandler() {
    return swipeEventHandler;
  }

在Activity/Fragment中:

开始观察:

 viewModel
    .getSwipeEventHandler()
    .observe(
        getViewLifecycleOwner(),
        new EventObserver<Boolean>() {
          @Override
          protected void onEvent(@NonNull Boolean content) {
            if(content)confirmDelete(modifier);
          }
        });

创建活动:

viewModel.getSwipeEventHandler().create(true);

2021 年更新:

使用协同程序库和 Flow,现在可以很容易地通过实现 Channels:

来实现这一点

MainActivity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import com.plcoding.kotlinchannels.databinding.ActivityMainBinding
import kotlinx.coroutines.flow.collect

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: MainViewModel

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        binding.btnShowSnackbar.setOnClickListener {
            viewModel.triggerEvent()
        }

        lifecycleScope.launchWhenStarted {
            viewModel.eventFlow.collect { event ->
                when(event) {
                    is MainViewModel.MyEvent.ErrorEvent -> {
                        Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG).show()
                    }
                }
            }
        }

    }
}

MainViewModel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {

    sealed class MyEvent {
        data class ErrorEvent(val message: String): MyEvent()
    }

    private val eventChannel = Channel<MyEvent>()
    val eventFlow = eventChannel.receiveAsFlow()

    fun triggerEvent() = viewModelScope.launch {
        eventChannel.send(MyEvent.ErrorEvent("This is an error"))
    }
}