You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ViewModel中合并多个Observable并优化重复请求?

解决重复接口请求与进度条频繁切换问题

Hey there, let's break down what's going on here and fix it properly. The core issue right now is that each of your repository methods (getCategoryList, getAdList, getUserList) is making a separate call to indexApi.getIndex()—so when you call all three load methods in the fragment, you're hitting the same API three times. That's not just inefficient, it's why your progress bar is flipping between states constantly.

Step 1: Refactor the Repository to Avoid Duplicate Requests

We'll create a single method to fetch the full home data once, save all parts to the database, and then let each data type be observed directly from Room (which supports LiveData for automatic updates).

Here's the updated HomeRepository.kt:

class HomeRepository @Inject constructor(
    private val indexApi: IndexApi,
    private val categoryDao: CategoryDao,
    private val userDao: UserDao,
    private val adDao: AdDao
) {
    // 直接返回Room的LiveData,数据库更新时自动通知UI
    fun observeCategoryList(): LiveData<List<Category>> = categoryDao.getCategoryList()
    fun observeAdList(): LiveData<List<Ad>> = adDao.getAdList()
    fun observeUserList(): LiveData<List<User>> = userDao.getUserList()

    // 统一获取并保存所有首页数据,只发起一次API请求
    fun fetchAndSaveHomeData(): Observable<Unit> {
        return indexApi.getIndex()
            .subscribeOn(Schedulers.io())
            .flatMapCompletable { response ->
                // 批量插入所有数据到数据库,用Completable处理无返回值的操作
                Completable.mergeArray(
                    categoryDao.insertCategoryList(response.data.categories).toCompletable(),
                    adDao.insertAdList(response.data.ad).toCompletable(),
                    userDao.insertUserList(response.data.status).toCompletable() // 根据实际数据结构调整
                )
            }
            .toObservable()
            .observeOn(AndroidSchedulers.mainThread())
    }
}

Note: Make sure your DAO methods return Completable or Single for insert operations (Room supports this out of the box if you define them correctly). For example:

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCategoryList(categories: List<Category>): Completable

Step 2: Simplify ViewModel Logic

Now we'll manage a single loading state for the API request, and expose the database-backed LiveData for each data type.

Updated HomeViewModel.kt:

class HomeViewModel @Inject constructor(
    private val homeRepository: HomeRepository
) : BaseViewModel() {
    private val _loadingState = MutableLiveData<Resource<Unit>>()
    val loadingState: LiveData<Resource<Unit>> = _loadingState

    // 直接暴露数据库的LiveData给UI
    val categoryList = homeRepository.observeCategoryList()
    val adList = homeRepository.observeAdList()
    val userList = homeRepository.observeUserList()

    fun loadHomeData() {
        _loadingState.postValue(Resource.loading())
        compositeDisposable.add(
            homeRepository.fetchAndSaveHomeData()
                .subscribe({
                    _loadingState.postValue(Resource.succeed(Unit))
                }, { error ->
                    _loadingState.postValue(Resource.error(error))
                })
        )
    }
}

Step 3: Clean Up the Fragment

Now we only need to trigger the data load once, and use the single loading state to control the progress bar. Each data list is observed separately to update its UI component.

Updated HomeFragment.kt:

fun init() {
    // ...其他初始化代码
    viewModel.loadHomeData()

    // 监听全局加载状态控制进度条
    viewModel.loadingState.observe(viewLifecycleOwner) { resource ->
        when(resource.status) {
            Status.LOADING -> progressBar.toVisible()
            Status.SUCCEED, Status.FAILED -> progressBar.toGone()
        }
    }

    // 观察分类数据更新UI
    viewModel.categoryList.observe(viewLifecycleOwner) { categories ->
        // 更新分类列表UI
    }

    // 观察广告数据更新UI
    viewModel.adList.observe(viewLifecycleOwner) { ads ->
        // 更新广告UI
    }

    // 观察用户数据更新UI
    viewModel.userList.observe(viewLifecycleOwner) { users ->
        // 更新用户列表UI
    }
}

Key Best Practices to Follow

  • Single Source of Truth Done Right: All UI data comes directly from Room. The API only acts as a way to refresh the database—this aligns perfectly with the single data source principle.
  • Batch API Requests: Always fetch all related data in one API call instead of multiple separate requests. This reduces network overhead and avoids race conditions.
  • Leverage Room's LiveData: Room automatically emits updates when the database changes, so you don't have to manually refresh UI data after inserting new records.
  • Unified Loading State: Use a single loading state for related data requests instead of multiple independent states. This prevents UI glitches like your progress bar flickering.
  • Avoid Nested Subscriptions: In your original code, you had a nested Observable.create inside a map—this is error-prone and can cause memory leaks. Use RxJava's flatMap, flatMapCompletable, or doOnNext operators instead for sequential operations.

内容的提问来源于stack exchange,提问作者Benfactor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 09:58:13