使用Paging Library更新单个元素的最优方案是什么?
刚好之前在类似的纯网络分页场景踩过坑,结合谷歌的《Paging with network》示例,给你分享两种可行方案,从手动维护缓存到更优雅的本地缓存方案:
方案一:手动维护内存缓存+局部刷新(适合纯网络无本地缓存的场景)
因为PageKeyedDataSource本身不会自动维护已加载数据的内存缓存,所以我们需要自己实现缓存管理,再配合局部刷新来避免全局invalidate():
在DataSource中维护内存缓存
改造你的PageKeyedSubredditDataSource,添加一个可变列表存储已加载的所有RedditPost,每次加载成功一页后就把数据追加到缓存里:class PageKeyedSubredditDataSource(...) : PageKeyedDataSource<String, RedditPost>() { // 维护已加载的所有帖子缓存 private val loadedPosts = mutableListOf<RedditPost>() override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, RedditPost>) { // 网络请求逻辑... val posts = response.body()?.data?.children?.map { it.data } ?: emptyList() loadedPosts.addAll(posts) callback.onResult(posts, null, nextPageKey) } override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, RedditPost>) { // 网络请求逻辑... val posts = response.body()?.data?.children?.map { it.data } ?: emptyList() loadedPosts.addAll(posts) callback.onResult(posts, nextPageKey) } // 其他方法... }添加单个元素更新方法
在DataSource中新增方法,用来查找并更新缓存中的目标帖子,同时返回缓存索引供后续定位:// 在DataSource内添加 fun updateSinglePost(updatedPost: RedditPost): Int? { val index = loadedPosts.indexOfFirst { it.id == updatedPost.id } if (index != -1) { loadedPosts[index] = updatedPost // 返回缓存中的索引,后续用来定位PagedList中的位置 return index } return null }ViewModel中协调更新并通知适配器
在ViewModel中持有DataSource和当前的PagedList引用,调用更新方法后找到元素在PagedList中的位置,通过LiveData通知UI层刷新:class SubredditViewModel(...) : ViewModel() { private var dataSource: PageKeyedSubredditDataSource? = null private val _postUpdatePosition = MutableLiveData<Int>() val postUpdatePosition: LiveData<Int> = _postUpdatePosition var currentPagedList: PagedList<RedditPost>? = null // 初始化PagedList的逻辑... fun initPagedList() { val config = PagedList.Config.Builder() .setPageSize(20) .build() val dataSourceFactory = object : DataSource.Factory<String, RedditPost>() { override fun create(): DataSource<String, RedditPost> { dataSource = PageKeyedSubredditDataSource(...) return dataSource!! } } val livePagedList = LivePagedListBuilder(dataSourceFactory, config).build() livePagedList.observeForever { currentPagedList = it } } fun updatePost(updatedPost: RedditPost) { dataSource?.updateSinglePost(updatedPost)?.let { cacheIndex -> currentPagedList?.let { pagedList -> // 找到元素在PagedList中的位置(通过id匹配) val pagedIndex = pagedList.indexOfFirst { it.id == updatedPost.id } if (pagedIndex != -1) { _postUpdatePosition.postValue(pagedIndex) } } } } }UI层接收通知并刷新
在Activity/Fragment中观察ViewModel的postUpdatePosition,收到位置后调用适配器的notifyItemChanged():viewModel.postUpdatePosition.observe(this) { position -> adapter.notifyItemChanged(position) }
方案二:结合Room本地缓存(最优解,符合Paging设计理念)
上面的方案需要手动维护缓存,容易出错。谷歌更推荐网络数据+本地缓存的架构,把分页数据持久化到Room,这样更新单个元素只需要修改数据库,Paging会自动感知变化并刷新对应项,完全不需要手动处理缓存和刷新:
定义Room实体和DAO
把RedditPost定义为Room实体,添加DAO方法用来查询分页数据、更新单个帖子:@Entity(tableName = "reddit_posts") data class RedditPost( @PrimaryKey val id: String, val title: String, val author: String, // 其他字段... ) @Dao interface RedditPostDao { @Query("SELECT * FROM reddit_posts ORDER BY created_utc DESC") fun getPagedPosts(): DataSource.Factory<Int, RedditPost> @Update suspend fun updatePost(post: RedditPost) }改造数据源为网络+本地架构
如果是Paging 3,使用RemoteMediator处理网络请求,把加载到的数据插入Room;如果是Paging 2,让PageKeyedDataSource加载网络数据后插入Room,PagedList直接从Room获取数据。更新单个元素只需操作Room
当需要更新某个RedditPost时,直接调用DAO的updatePost()方法:viewModelScope.launch { redditPostDao.updatePost(updatedPost) }Room会自动通知Paging库数据变化,Paging会生成包含更新后元素的新PagedList,并且只会刷新对应的列表项,不会重新加载所有页面,完美符合你的需求。
内容的提问来源于stack exchange,提问作者Damia Fuentes

