每当您在 LiveData 上调用.observe()
时,观察者都会收到该 LiveData 的最后一个值。这在某些情况下可能有用,但在我的情况下没有用。
-
每当我调用
.observe()
时,我都希望观察者只接收未来的 LiveData 更改,而不是调用.observe()
时它所持有的值。 -
我可能有多个 LiveData 实例的观察者。我希望他们都能在发生时收到实时数据更新。
-
我希望每个观察者只使用一次 LiveData 更新。我认为只是对第一个要求的重新措辞,但我的头已经在旋转,我不确定。
在谷歌搜索这个问题时,我遇到了两种常见的方法:
-
将数据包装在
LiveData<SingleEvent<Data>>
中,并检查此SingleEvent
类是否已使用。 -
扩展
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)
}
}
})
}