让每个观察者仅在 subscribing/observing 时接收 *new* LiveData
Let every Observer only receive *new* LiveData upon subscribing/observing
每当您在 LiveData 上调用 .observe()
时,Observer 都会收到该 LiveData 的最后一个值。这在某些情况下可能有用,但在我的情况下没有用。
每当我调用 .observe()
时,我希望 Observer 只接收未来的 LiveData 更改,而不是调用 .observe()
时它持有的值。
对于一个 LiveData 实例,我可能有多个 Observer。我希望它们在发生时都能收到 LiveData 更新。
我希望每个观察者只使用一次 LiveData 更新。我认为这只是对第一个要求的重新措辞,但我的头脑已经在旋转了,我不确定。
在谷歌搜索这个问题时,我想到了两种常见的方法:
将数据包装在 LiveData<SingleEvent<Data>>
中,如果它已经被消耗,则检查此 SingleEvent
class。
扩展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 设计:
- 2 ListFragment 正在显示条目列表。
- 他们正在使用同一 ViewModel class 的 2 个实例来观察 UI 相关的 LiveData。
- 用户可以删除此类 ListFragment 中的条目。删除是由调用
Repository.delete()
的 ViewModel 完成的
- ViewModel 观察
RepositoryEvents
的存储库。
因此,当删除完成后,Repository 会通知 ViewModel,ViewModel 会通知 ListFragment。
现在,当用户切换到第二个 ListFragment 时,会发生以下情况:
- 创建第二个 Fragment 并在其 ViewModel
上调用 .observe()
创建 ViewModel 并在存储库上调用 .observe()
存储库将其当前 RepositoryEvent
发送到 ViewModel
- ViewModel 将相应的 UI 事件发送到 Fragment
- 该片段显示了一个确认 Snackbar 以确认发生在其他地方的删除。
下面是一些简化的代码:
片段:
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"))
}
}
每当您在 LiveData 上调用 .observe()
时,Observer 都会收到该 LiveData 的最后一个值。这在某些情况下可能有用,但在我的情况下没有用。
每当我调用
.observe()
时,我希望 Observer 只接收未来的 LiveData 更改,而不是调用.observe()
时它持有的值。对于一个 LiveData 实例,我可能有多个 Observer。我希望它们在发生时都能收到 LiveData 更新。
我希望每个观察者只使用一次 LiveData 更新。我认为这只是对第一个要求的重新措辞,但我的头脑已经在旋转了,我不确定。
在谷歌搜索这个问题时,我想到了两种常见的方法:
将数据包装在
LiveData<SingleEvent<Data>>
中,如果它已经被消耗,则检查此SingleEvent
class。扩展
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 设计:
- 2 ListFragment 正在显示条目列表。
- 他们正在使用同一 ViewModel class 的 2 个实例来观察 UI 相关的 LiveData。
- 用户可以删除此类 ListFragment 中的条目。删除是由调用
Repository.delete()
的 ViewModel 完成的
- ViewModel 观察
RepositoryEvents
的存储库。
因此,当删除完成后,Repository 会通知 ViewModel,ViewModel 会通知 ListFragment。
现在,当用户切换到第二个 ListFragment 时,会发生以下情况:
- 创建第二个 Fragment 并在其 ViewModel 上调用
创建 ViewModel 并在存储库上调用
.observe()
存储库将其当前
RepositoryEvent
发送到 ViewModel- ViewModel 将相应的 UI 事件发送到 Fragment
- 该片段显示了一个确认 Snackbar 以确认发生在其他地方的删除。
.observe()
下面是一些简化的代码:
片段:
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"))
}
}