让每个观察者仅在订阅/观察时收到*新* LiveData

问题描述 投票:0回答:4

每当您在 LiveData 上调用

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

  1. 每当我调用

    .observe()
    时,我希望观察者仅接收未来的 LiveData 更改,而不是调用
    .observe()
    时它所持有的值。

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

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


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

  1. 将数据包装在一个

    LiveData<SingleEvent<Data>>
    中,如果它已经被消费,则检查这个
    SingleEvent
    类。

  2. 如果观察者已经得到事件

    ,则扩展

    MediatorLiveData
  3. 并使用查找图

可以在此处找到这些方法的示例: 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

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


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

我正在关注 Android Architecture Componentns 的 LiveData MVVM 设计:

  • 2 ListFragment 显示条目列表。
  • 他们正在使用同一个 ViewModel 类的 2 个实例来观察与 UI 相关的 LiveData。
  • 用户可以删除此类 ListFragment 中的条目。删除是由 ViewModel 调用
    Repository.delete()
  • 完成的
  • ViewModel 为
    RepositoryEvents
    观察存储库。

所以当删除完成后,Repository 通知 ViewModel,ViewModel 通知 ListFragment。

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

  • 第二个 Fragment 被创建并在其 ViewModel 上调用
    .observe()
  • ViewModel 被创建并在 Repository 上调用

    .observe()

  • 存储库将其当前

    RepositoryEvent
    发送到ViewModel

  • ViewModel 将相应的 UI 事件发送到 Fragment
  • 片段显示一个确认 Snackbar 以确认发生在其他地方的删除。

这里是一些简化的代码:

片段:

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

视图模型:

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)
}
android android-lifecycle android-architecture-components android-livedata android-mvvm
4个回答
2
投票

2021 年更新:

使用协程库和 Flow,现在可以很容易地通过实施实现这一点

Channels

主要活动

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"))
    }
}

0
投票

对我来说问题是这样解决的:

事件包装类来保存事件相关数据(从谷歌示例中复制)

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

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

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) {}
}

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

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

在活动/片段中:

开始观察:

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

创建事件:

viewModel.getSwipeEventHandler().create(true);

0
投票

根据需要创建了一个基本的密封类标志:

sealed class Event(private var handled: Boolean = false) {

        val coldData: Event?
            get() {
                return if (handled) null else {
                    handled = true
                    this
                }
            }

        class ShowLoader() : Event()
        class HideLoader() : Event()
        class ShowErrorAlert(@StringRes val message: Int) : Event()
    }

然后可以在不同的片段观察

viewModel.eventFlow.observe(this) { event ->

            val data = event.coldData

            when (data) {
                is Event.ShowLoader -> {
                    progressBar.visible = true
                }
                is Event.HideLoader -> {
                    progressBar.visible = false
                }
                is Event.ShowErrorAlert -> {
                    showAlert(data.message)
                }
                else -> {
                    // do nothing
                }
            }
        }

或者使用具有相同目的的

MutableLiveData
的子类来单独处理它们。


0
投票

您可以在扩展功能中添加此功能。只需用它替换对

observe
的调用即可。它只会发出观察LiveData对象后发出的事件。

fun <T> LiveData<T>.observeFutureEvents(owner: LifecycleOwner, observer: Observer<T>) {
    observe(owner, object : Observer<T> {
        var isFirst = true

        override fun onChanged(value: T) {
            if (isFirst) {
                isFirst = false
            } else {
                observer.onChanged(value)
            }
        }
    })
}
© www.soinside.com 2019 - 2024. All rights reserved.