使用Navigation架构组件+底部导航 避免ViewModel重复请求数据
我之前也碰到过一模一样的问题——用Navigation Component做底部导航,来回切换Fragment的时候,每次都会触发数据请求,次数多了不仅浪费资源,还可能导致UI抖动。咱们可以从Fragment生命周期控制、ViewModel缓存和Repository优化三个层面来解决:
1. 先阻止Fragment不必要的重建(最直接的第一步)
Navigation Component默认每次切换底部导航项时,都会销毁并重建目标Fragment。咱们可以给导航图里的每个Fragment加上app:launchSingleTop="true",这样切换时会复用已存在的Fragment实例,不会走销毁重建流程,自然就不会重复调用onActivityCreated里的getData()了。
修改你的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" app:launchSingleTop="true" <!-- 新增这行 --> tools:layout="@layout/fragment_home"/> <fragment android:id="@+id/search" android:name="com.nux.ui.search.SearchFragment" android:label="@string/title_search" app:launchSingleTop="true" <!-- 新增这行 --> tools:layout="@layout/fragment_search" /> <fragment android:id="@+id/my_profile" android:name="com.nux.ui.user.MyProfileFragment" android:label="@string/title_profile" app:launchSingleTop="true" <!-- 新增这行 --> tools:layout="@layout/fragment_profile" /> </navigation>
2. ViewModel层缓存请求结果,避免相同输入重复调用Repository
即使Fragment还是会重建(比如配置变更),咱们也要确保ViewModel不会重复发起相同的请求。可以在ViewModel里加一个缓存Map,存储已经请求过的输入对应的结果,当输入相同时直接返回缓存,不用再调用Repository。
修改你的ProductsViewModel:
class ProductsViewModel @Inject constructor(private val repository: ProductsRepository) : ViewModel() { private val _input = MutableLiveData<PInput>() // 缓存输入参数与请求结果的映射 private val inputResultCache = mutableMapOf<PInput, ApiResponse<ProductsQuery.Data>>() fun setInput(filters: ProductsFilters, limit: Int) { val newInput = PInput(filters, limit) // 只有当输入变化,或者缓存里没有该输入的结果时,才更新LiveData if (_input.value != newInput || !inputResultCache.containsKey(newInput)) { _input.value = newInput } } private val getProducts = map(_input) { input -> // 从缓存获取,没有则调用Repository请求并缓存 inputResultCache.getOrPut(input) { repository.getProducts(input.filters, input.limit) } } val products = switchMap(getProducts) { it.data } val networkState = switchMap(getProducts) { it.networkState } }
3. 修复Repository的NetworkState问题(避免状态混乱)
你的Repository里networkState是成员变量,这会导致所有请求共用同一个状态LiveData——比如多个请求同时发起时,状态会互相覆盖。咱们要改成每个请求创建独立的networkState:
修改你的ProductsRepository:
@Singleton class ProductsRepository @Inject constructor(private val api: ApolloClient) { fun getProducts(filters: ProductsFilters, limit: Int): ApiResponse<ProductsQuery.Data> { val products = MutableLiveData<ProductsQuery.Data>() // 每个请求创建独立的NetworkState LiveData val networkState = MutableLiveData<NetworkState>() 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) } }
额外优化:Fragment里的请求触发逻辑
如果还是担心Fragment重建时重复调用setInput,可以在Fragment的getData()里判断savedInstanceState——如果不为null(说明是重建而非首次创建),就不用再调用setInput:
修改HomeFragment的getData():
private fun getData() { val productsFilters = ProductsFilters.builder().sortBy(SortProductsBy.NEWEST).build() // 只有首次创建Fragment时才触发请求 if (savedInstanceState == null) { productsViewModel.setInput(productsFilters, 2) } // 观察数据和状态的逻辑保持不变 productsViewModel.products.observe(viewLifecycleOwner, Observer { it.products()?.let { products -> productsAdapter.setData(products) } }) productsViewModel.networkState.observe(viewLifecycleOwner, Observer { //Todo showing progress bar }) }
这样一套组合拳下来,不管是底部导航切换还是配置变更,都不会重复发起不必要的网络请求了。
内容的提问来源于stack exchange,提问作者Nux

