使用 ViewModel 重新创建片段时如何执行一次性提取操作

How to do one time fetch operations when Fragments are recreated with ViewModel

我进入 FragmentA() ,在我进入 FragmentA() 之后,我将数据获取到我的服务器,当这些数据出现时我填充了一个列表。

现在,如果我从 FragmentA() 转到 FragmentB() 并从 FragmentB() 我按下后退按钮或导航回 FragmentA() ,它将列表重新提取到服务器并再次重新填充列表。

我不想要这个,相反,我希望我的 viewmodel 方法不再触发,我正在寻求帮助因为导航组件不允许我执行 .add 操作来保存我的状态 片段A()

有没有办法将此操作作为一次获取​​操作来执行,而不是每次我在执行后压时从 FragmentB() 转到 FragmentA() 时都重新获取?

片段A()

 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
    }

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

如何在 FragmentA() 处仅获取一次,将这些值保留在视图模型中,然后在尝试重新获取时不再执行此操作?

我非常想知道如何才能完成这项工作!

谢谢

万能的google,在其latest updated best practice architecture components github repository中是这样的:

    fun setId(owner: String, name: String) {
        val update = RepoId(owner, name)
        if (_repoId.value == update) {
            return
        }
        _repoId.value = update
    }

这样,即使您的片段被重新创建,只要视图模型还活着,您就不会从服务器请求新数据,但 livedata 会再次发送其最新数据,因此片段可以更新其视图。

此外,您是否认为 locationQuery.distinctUntilChanged() 不起作用?您是否正在监控是否有请求发送到您的服务器?

感谢 Henrique,我用更简单的方法解决了这个问题

private var shouldFetchAgain = true

    fun fetchShopList(shopId: String, location: String) = liveData(Dispatchers.IO) {

        if (shouldFetchAgain) {
            emit(Resource.Loading())
            try {
                emit(repo.getShopList(shopId, location))
                shouldFetchAgain = false
            } catch (e: Exception) {
                emit(Resource.Failure(e))
            }
        }
    }

我知道这不安全,因为我不能将多个观察者添加到此,但对于我的用例,我只有 1 个观察者用于此数据

不要在 onViewCreated 中调用 fetchShops。相反,仅在 ViewModel 的 init 块中调用它,将数据缓存在 ViewModel 的一个字段中,并在 onViewCreated 中观察该值。只要您的 ViewModel 还活着(当 Fragment A 在后台堆栈上时应该是这种情况),它就不会再次 运行 init 块。