【问题标题】:ViewModel refetching data again with distinctUntilChanged()ViewModel 再次使用 distinctUntilChanged() 重新获取数据
【发布时间】:2020-05-11 22:43:32
【问题描述】:

我有一个片段,我想对其数据进行一次提取,我使用distinctUntilChanged() 仅提取一次,因为在此片段期间我的位置没有改变。

片段

 private val viewModel by viewModels<LandingViewModel> {
        VMLandingFactory(
            LandingRepoImpl(
                LandingDataSource()
            )
        )
    }

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val sharedPref = requireContext().getSharedPreferences("LOCATION", Context.MODE_PRIVATE)
        val nombre = sharedPref.getString("name", null)
        location = name!!
    }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupRecyclerView()
        fetchShops(location)
    }

 private fun fetchShops(localidad: String) {

        viewModel.setLocation(location.toLowerCase(Locale.ROOT).trim())
        viewModel.fetchShopList
            .observe(viewLifecycleOwner, Observer {

                when (it) {

                    is Resource.Loading -> {
                        showProgress()
                    }
                    is Resource.Success -> {
                        hideProgress()
                        myAdapter.setItems(it.data)
                    }
                    is Resource.Failure -> {
                        hideProgress()
                        Toast.makeText(
                            requireContext(),
                            "There was an error loading the shops.",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            })

    }

视图模型

 private val locationQuery = MutableLiveData<String>()

    fun setLocation(location: String) {
        locationQuery.value = location
    }

    val fetchShopList = locationQuery.distinctUntilChanged().switchMap { location ->
        liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
            emit(Resource.Loading())
            try{
                emit(repo.getShopList(location))
            }catch (e:Exception){
                emit(Resource.Failure(e))
            }
        }
        }

现在,如果我转到下一个片段并按回,这会再次触发,我知道这可能是因为片段正在重新创建然后传递一个新的视图模型实例,这就是为什么不保留该位置的原因,但是如果我把activityViewModels作为viewmodel的实例,它也发生了同样的情况,数据在backpress上再次加载,这是不可接受的,因为每次返回都会获取数据,这对我来说不是服务器效率,我需要当用户在此片段中时仅获取此数据,并且如果他们按下回不再次获取它。

有什么线索吗?

我正在使用导航组件,所以我不能使用 .add 或进行片段事务,我想在第一次创建这个片段时只获取一次,而不是在下一个片段的后台重新获取

【问题讨论】:

  • 要明确一点:问题是 fetchShopList 多次发出相同的值,还是重新计算一遍?您可以简单地通过在计算值的位置放置一个断点并进行调试来查看它是否命中断点。
  • 它再次发出@Boda,感谢您的时间

标签: android android-fragments kotlin viewmodel android-architecture-components


【解决方案1】:

TL;DR

您需要使用仅发出一次事件的 LiveData,即使用户界面重新订阅它。有关更多信息和解释以及修复方法,请继续阅读。

当您从 Fragment 1 -> Fragment 2 开始时,Fragment 1 实际上并没有立即销毁,它只是取消订阅您的 ViewModel LiveData。

现在,当您从 F2 回到 F1 时,片段将重新订阅 ViewModel LiveData,并且由于 LiveData 本质上是状态持有者,因此它将立即重新发出其最新值,从而导致ui 重新绑定。

您需要的是某种 LiveData,它不会发出之前已发出的事件。

这是 LiveData 的常见用例,有一篇非常好的文章讨论了针对不同类型的用例需要类似的 LiveData,您可以阅读它here。

虽然文章提出了几个解决方案,但有时这些解决方案可能有点矫枉过正,因此更简单的解决方案是使用以下ActionLiveView

// First extend the MutableLiveData class
class ActionLiveData<T> : MutableLiveData<T>() {

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<T?>) {

       // Being strict about the observer numbers is up to you
       // I thought it made sense to only allow one to handle the event
        if (hasObservers()) {
            throw Throwable("Only one observer at a time may subscribe to a ActionLiveData")
        }

        super.observe(owner, Observer { data ->
            // We ignore any null values and early return
            if (data == null)  return
            observer.onChanged(data)
            // We set the value to null straight after emitting the change to the observer
            value = null
            // This means that the state of the data will always be null / non existent
            // It will only be available to the observer in its callback and since we do not emit null values
            // the observer never receives a null value and any observers resuming do not receive the last event.
            // Therefore it only emits to the observer the single action so you are free to show messages over and over again
            // Or launch an activity/dialog or anything that should only happen once per action / click :).
        })
    }

    // Just a nicely named method that wraps setting the value
    @MainThread
    fun sendAction(data: T) {
        value = data
    }
}

如果需要,您可以在this link 中找到更多关于 ActionLiveData 的说明。

我建议使用 ActionLiveData 类,我一直将它用于中小型项目,到目前为止它运行良好,但同样,您比我更了解您的用例。 :)

【讨论】:

    猜你喜欢
    • 2020-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-05
    • 1970-01-01
    • 1970-01-01
    • 2020-12-31
    • 1970-01-01
    相关资源
    最近更新 更多