RecyclerView分页数据切换问题:Profile页帖子与点赞帖切换显示
如何在ProfileFragment中切换显示用户发布帖与点赞帖?
问题描述
我有一个作为用户资料页的Fragment,默认需要显示用户发布的帖子,点击按钮后切换显示用户点赞的帖子。
初始代码在页面创建时能正常显示用户发布的帖子,但添加按钮切换点赞帖的逻辑后出现问题:如果同时在onViewCreated中收集profilePosts和profileLikedPosts两个Flow,进入页面时不会显示用户发布的帖子,只有点击按钮后才会显示点赞帖。
相关代码如下:
Fragment初始代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 获取用户发布帖 viewModel.getProfilePosts(profileId) val adapter = PostAdapter(this) // 收集发布帖并提交给适配器 viewLifecycleOwner.lifecycleScope.launch { viewModel.profilePosts.collect { adapter.submitData(it) } } }
切换点赞帖的按钮逻辑
private fun getProfileLikesPosts(profileId: Int) { viewModel.getProfileLikes(profileId) }
错误的尝试(同时收集两个Flow)
viewLifecycleOwner.lifecycleScope.launch { viewModel.profileLikedPosts.collect { adapter.submitData(it) } }
ViewModel相关代码
// 点赞帖的Flow定义 private val _profileLikedPosts = MutableStateFlow<PagingData<PostResponse>>(PagingData.empty()) val profileLikedPosts = _profileLikedPosts // 获取点赞帖的实现 fun getProfileLikes(profileId: Int) { viewModelScope.launch { repository.getProfileLikedPosts(profileId).cachedIn(viewModelScope).collect { _profileLikedPosts.value = it } } }
错误原因
- Flow提交冲突:同时启动两个协程分别收集
profilePosts和profileLikedPosts,并都调用adapter.submitData()。由于profileLikedPosts初始值是PagingData.empty(),它会覆盖profilePosts提交的数据,导致页面初始显示为空。 - 无统一数据源控制:切换时没有明确控制哪个Flow的数据提交给适配器,导致逻辑混乱,无法正确切换显示内容。
解决方案
方案一:ViewModel中维护统一的显示Flow(推荐)
在ViewModel中新增一个统一的currentDisplayPosts Flow,用来管理当前要显示的帖子数据,切换时只需更新这个Flow的指向即可,Fragment只需要收集这一个Flow。
修改后的ViewModel代码
private val _profilePosts = MutableStateFlow<PagingData<PostResponse>>(PagingData.empty()) val profilePosts = _profilePosts.asStateFlow() private val _profileLikedPosts = MutableStateFlow<PagingData<PostResponse>>(PagingData.empty()) val profileLikedPosts = _profileLikedPosts.asStateFlow() // 统一的显示流,默认初始为空 private val _currentDisplayPosts = MutableStateFlow<PagingData<PostResponse>>(PagingData.empty()) val currentDisplayPosts = _currentDisplayPosts.asStateFlow() // 初始化加载用户发布帖,并设置为默认显示内容 fun initProfilePosts(profileId: Int) { viewModelScope.launch { repository.getProfilePosts(profileId).cachedIn(viewModelScope).collect { _profilePosts.value = it // 初始时将发布帖设置为当前显示内容 _currentDisplayPosts.value = it } } } // 加载用户点赞帖 fun loadProfileLikes(profileId: Int) { viewModelScope.launch { repository.getProfileLikedPosts(profileId).cachedIn(viewModelScope).collect { _profileLikedPosts.value = it } } } // 切换到显示用户发布帖 fun switchToOwnPosts() { _currentDisplayPosts.value = _profilePosts.value } // 切换到显示用户点赞帖 fun switchToLikedPosts() { _currentDisplayPosts.value = _profileLikedPosts.value }
修改后的Fragment代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val adapter = PostAdapter(this) // 初始化加载并显示用户发布帖 viewModel.initProfilePosts(profileId) // 只收集统一的显示流,无需多个协程 viewLifecycleOwner.lifecycleScope.launch { viewModel.currentDisplayPosts.collect { adapter.submitData(it) } } // 切换到点赞帖的按钮点击事件 btnLikedPosts.setOnClickListener { // 如果点赞帖还未加载,先触发加载 if (viewModel.profileLikedPosts.value == PagingData.empty()) { viewModel.loadProfileLikes(profileId) } viewModel.switchToLikedPosts() } // 切换回发布帖的按钮点击事件 btnOwnPosts.setOnClickListener { viewModel.switchToOwnPosts() } }
方案二:Fragment中管理协程切换收集目标
如果不想修改ViewModel结构,可以在Fragment中用一个Job来管理当前收集数据的协程,切换时取消旧协程,启动新协程收集对应Flow的数据。
修改后的Fragment代码
private var collectJob: Job? = null private lateinit var adapter: PostAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) adapter = PostAdapter(this) // 初始收集发布帖 startCollecting(viewModel.profilePosts) viewModel.getProfilePosts(profileId) // 切换点赞帖按钮 btnLikedPosts.setOnClickListener { viewModel.getProfileLikes(profileId) startCollecting(viewModel.profileLikedPosts) } // 切换发布帖按钮 btnOwnPosts.setOnClickListener { viewModel.getProfilePosts(profileId) startCollecting(viewModel.profilePosts) } } private fun startCollecting(targetFlow: StateFlow<PagingData<PostResponse>>) { // 取消之前的收集任务,避免多个Flow同时提交数据 collectJob?.cancel() collectJob = viewLifecycleOwner.lifecycleScope.launch { targetFlow.collect { adapter.submitData(it) } } }
说明
- 方案一的优势在于将数据源的切换逻辑封装在ViewModel中,Fragment只负责UI交互和数据展示,符合MVVM架构原则,且已加载的数据会被缓存,切换更流畅。
- 方案二更适合快速修改,但需要手动管理协程生命周期,相对繁琐。
内容的提问来源于stack exchange,提问作者user2896120
相关产品推荐
相关产品推荐

