让每个观察者在订阅/观察后​​仅接收* new * LiveData

时间:2019-04-27 17:58:09

标签: android android-lifecycle android-architecture-components android-livedata android-mvvm

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

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

  2. 我可能有多个LiveData实例的观察者。我希望他们在发生时都能收到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

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


为了给您一些见解,我在说什么/我在编码什么:

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

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

因此,删除完成后,存储库会将其通知给ViewModel,而ViewModel会将其通知给ListFragment。

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

  • 第二个片段被创建并在其ViewModel上调用.observe()
  • 已创建ViewModel并在存储库上调用.observe()

  • 存储库将其当前的RepositoryEvent发送到ViewModel

  • ViewModel将相应的UI事件发送到片段
  • 该片段显示一个确认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)
}

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

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

事件包装器类,用于保留与事件相关的数据(从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;
    }
}

接下来,我创建事件观察器类,该类处理数据检查(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) {}
}

此外,事件处理程序类用于简化从viewmodel的访问:

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