ViewModel 在重新创建片段时重新获取数据

ViewModel refetches data when fragment is recreated

我正在使用 Bottom Navigation with Navigation Architecture Component。当用户从一个项目导航到另一个项目(通过底部导航)并再次返回时,视图模型调用存储库函数再次获取数据。因此,如果用户来回移动 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
        })
    }
}

ViewModel

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>

ViewModelFactory

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

onActivityCreated(),您正在呼叫getData()。在那里,你有:

productsViewModel.setInput(productsFilters, 2)

这反过来会改变 ProductsViewModel_input 的值。而且,每次 _input 更改时,都会计算 getProducts lambda 表达式,调用您的存储库。

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

我对您的应用了解不多,无法告诉您需要更改的内容。这里有一些可能性:

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

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

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

所以你可以调用你的函数 (getProducts) inside the "init" in viewModel to 运行 一次。

init {
        getProducts()
    }

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

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

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

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

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