让每个观察者只在订阅/观察时收到*新的*实时数据



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

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

  2. 我可能有多个 LiveData 实例的观察者。我希望他们都能在发生时收到实时数据更新。

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


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

  1. 将数据包装在LiveData<SingleEvent<Data>>中,并检查此SingleEvent类是否已使用。

  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

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


给你一些见解,我在说什么/我在编码什么:

我遵循Android Architecture Componentns的LiveData MVVM设计:

  • 2 列表片段显示条目列表。
  • 他们使用同一 ViewModel 类的 2 个实例来观察与 UI 相关的实时数据。
  • 用户可以删除此类列表片段中的条目。删除操作由调用Repository.delete()的视图模型完成
  • ViewModel 观察存储库以查找RepositoryEvents

因此,当删除完成后,存储库会通知 ViewModel,而 ViewModel 会通知 ListFragment

。现在,当用户切换到第二个列表片段时,将发生以下情况:

  • 创建第二个片段并在其视图模型上调用.observe()
  • 创建视图模型并在存储库上调用.observe()

  • 存储库将其当前RepositoryEvent发送到视图模型

  • 视图模型将相应的 UI 事件发送到片段
  • 片段显示一个确认的小吃栏,用于在其他地方发生的删除。

下面是一些简化的代码:

片段:

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

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

主视图模型

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

对我来说,问题通过以下方式得到解决:

事件包装类以保留事件相关数据(从谷歌示例复制)

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

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

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

例:

在视图模型中.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);

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

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子类单独处理它们。

您可以在扩展函数中添加此功能。只需将呼叫替换为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)
}
}
})
}

相关内容

最新更新