13

我正在使用带有导航架构组件的底部导航。当用户从一个项目导航到另一个项目(通过底部导航)并再次返回查看模型调用存储库函数以再次获取数据时。因此,如果用户来回移动 10 次,相同的数据将被提取 10 次。重新创建片段时如何避免重新获取数据已经存在?

分段

class HomeFragment : Fragment() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var productsViewModel: ProductsViewModel
    private lateinit var productsAdapter: ProductsAdapter

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_home, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initViewModel()
        initAdapters()
        initLayouts()
        getData()
    }

    private fun initViewModel() {
        (activity!!.application as App).component.inject(this)

        productsViewModel = activity?.run {
            ViewModelProviders.of(this, viewModelFactory).get(ProductsViewModel::class.java)
        }!!
    }

    private fun initAdapters() {
        productsAdapter = ProductsAdapter(this.context!!, From.HOME_FRAGMENT)
    }

    private fun initLayouts() {
        productsRecyclerView.layoutManager = LinearLayoutManager(this.activity)
        productsRecyclerView.adapter = productsAdapter
    }

    private fun getData() {
        val productsFilters = ProductsFilters.builder().sortBy(SortProductsBy.NEWEST).build()

        //Products filters
        productsViewModel.setInput(productsFilters, 2)

        //Observing products data
        productsViewModel.products.observe(viewLifecycleOwner, Observer {
            it.products()?.let { products -> productsAdapter.setData(products) }
        })

        //Observing loading
        productsViewModel.networkState.observe(viewLifecycleOwner, Observer {
            //Todo showing progress bar
        })
    }
}

视图模型

class ProductsViewModel
@Inject constructor(private val repository: ProductsRepository) : ViewModel() {

    private val _input = MutableLiveData<PInput>()

    fun setInput(filters: ProductsFilters, limit: Int) {
        _input.value = PInput(filters, limit)
    }

    private val getProducts = map(_input) {
        repository.getProducts(it.filters, it.limit)
    }

    val products = switchMap(getProducts) { it.data }
    val networkState = switchMap(getProducts) { it.networkState }
}

data class PInput(val filters: ProductsFilters, val limit: Int)

存储库

@Singleton
class ProductsRepository @Inject constructor(private val api: ApolloClient) {

    val networkState = MutableLiveData<NetworkState>()

    fun getProducts(filters: ProductsFilters, limit: Int): ApiResponse<ProductsQuery.Data> {
        val products = MutableLiveData<ProductsQuery.Data>()

        networkState.postValue(NetworkState.LOADING)

        val request = api.query(ProductsQuery
                .builder()
                .filters(filters)
                .limit(limit)
                .build())

        request.enqueue(object : ApolloCall.Callback<ProductsQuery.Data>() {
            override fun onFailure(e: ApolloException) {
                networkState.postValue(NetworkState.error(e.localizedMessage))
            }

            override fun onResponse(response: Response<ProductsQuery.Data>) = when {
                response.hasErrors() -> networkState.postValue(NetworkState.error(response.errors()[0].message()))
                else -> {
                    networkState.postValue(NetworkState.LOADED)
                    products.postValue(response.data())
                }
            }
        })

        return ApiResponse(data = products, networkState = networkState)
    }
}

导航 main.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation.xml"
    app:startDestination="@id/home">

    <fragment
        android:id="@+id/home"
        android:name="com.nux.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home"/>
    <fragment
        android:id="@+id/search"
        android:name="com.nux.ui.search.SearchFragment"
        android:label="@string/title_search"
        tools:layout="@layout/fragment_search" />
    <fragment
        android:id="@+id/my_profile"
        android:name="com.nux.ui.user.MyProfileFragment"
        android:label="@string/title_profile"
        tools:layout="@layout/fragment_profile" />
</navigation>

视图模型工厂

@Singleton
class ViewModelFactory @Inject
constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = viewModels[modelClass]
                ?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
                ?: throw IllegalArgumentException("unknown model class $modelClass")
        return try {
            creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

在此处输入图像描述

4

4 回答 4

5

onActivityCreated(),你在打电话getData()。在那里,你有:

productsViewModel.setInput(productsFilters, 2)

这反过来又会改变_input. ProductsViewModel而且,每次_input更改时,getProducts都会评估 lambda 表达式,调用您的存储库。

因此,每次onActivityCreated()调用都会触发对您的存储库的调用。

我对您的应用了解得不够多,无法告诉您需要更改的内容。以下是一些可能性:

  • 切换onActivityCreated()到其他生命周期方法。initViewModel()可以被调用onCreate(),而其余的应该被调用onViewCreated()

  • 重新考虑您的getData()实施。setInput()每次我们导航到这个片段时,你真的需要打电话吗?或者,这应该是其中的一部分initViewModel()并一次性完成onCreate()吗?或者,由于productsFilters似乎根本与片段无关,productsFilters因此setInput()调用应该是 的init块的一部分ProductsViewModel,所以它只发生一次?

于 2019-05-30T14:04:31.437 回答
5

一个简单的解决方案是在这行代码中将 ViewModelProvider 所有者从this更改为requireActivity() :

ViewModelProviders.of(this, viewModelFactory).get(ProductsViewModel::class.java)

因此,由于活动是视图模型的所有者,并且视图模型的生命周期附加到活动而不是片段,因此在活动内的片段之间导航不会重新创建视图模型。

于 2020-06-29T04:32:53.667 回答
0

当您通过底部导航选择其他页面并返回时,片段销毁并重新创建。因此onCreateonViewCreatedonActivityCreate 将再次运行。但是 viewModel 还活着。

因此,您可以在 viewModel 的“init”中调用您的函数 ( getProducts )来运行它一次。

init {
        getProducts()
    }
于 2020-06-22T04:24:34.367 回答
0

通过 mainActivity 中的静态定义 ProductsViewModel 并在 onCreate 方法中初始化。现在只需在片段中以这种方式使用它:

MainActivity.productsViewModel.products.observe(viewLifecycleOwner, Observer {
            it.products()?.let { products -> productsAdapter.setData(products) }
        })
于 2020-11-05T14:54:21.257 回答