如何在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
CompletableorSinglefor 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.createinside amap—this is error-prone and can cause memory leaks. Use RxJava'sflatMap,flatMapCompletable, ordoOnNextoperators instead for sequential operations.
内容的提问来源于stack exchange,提问作者Benfactor

