返回Fragment时LiveData观察者重复触发的问题解决
分类选择Fragment返回时LiveData重复触发跳转的解决方法
问题背景
我正在开发一款Android应用,在Fragment中使用RecyclerView展示分类列表。用户点击分类后,会跳转到同一个Fragment展示该分类的子分类;若该分类无子分类,则跳转到另一个Fragment展示对应商品列表。
问题:选中无子分类的分类时,能正常跳转到商品列表Fragment,但返回分类选择Fragment时,LiveData观察者会再次触发,重复显示空列表并执行跳转。尝试过移除观察者、使用SingleLiveEvent,都未解决问题。
需求:有子分类时显示分类选择Fragment,无子分类时跳转至商品列表Fragment,避免返回时LiveData观察者重复触发跳转。
现有代码
CategorySelectionFragment代码
@AndroidEntryPoint class CategorySelectionFragment : BaseFragment<FragmentCategorySelectionBinding>(FragmentCategorySelectionBinding::inflate) { private val arguments by navArgs<CategorySelectionFragmentArgs>() private val sharedViewModel by activityViewModels<HomeSharedViewModel>() private val categorySelectionViewModel by viewModels<CategorySelectionViewModel>() lateinit var categorySelectionAdapter: CategorySelectionAdapter override fun setupViews(view: View) { sharedViewModel.allCategory.value?.let { categorySelectionViewModel.addAllCategoryForCategorySelection( it ) } categorySelectionViewModel.getCategoryList(arguments.parentCategoryId) categorySelectionAdapter = CategorySelectionAdapter { findNavControllerSafely()?.navigate( MainHostFragmentDirections.actionMainHostFragmentToCategorySelectionFragment( it.id ) ) } binding.rvCategorySelection.apply { layoutManager = LinearLayoutManager(context) adapter = categorySelectionAdapter } } override fun setupListeners() { } override fun setupObservers() { categorySelectionViewModel.parentCategoryList.observe(viewLifecycleOwner) { if (it.isEmpty()) { // TODO: Move to the next activity screen findNavControllerSafely()?.navigate( CategorySelectionFragmentDirections.actionCategorySelectionFragmentToProductListFragment() ) return@observe } it?.let { categorySelectionAdapter.submitList(it) } } } }
CategorySelectionViewModel代码
@HiltViewModel class CategorySelectionViewModel @Inject constructor(private val resourcesProvider: ResourcesProvider) : ViewModel() { private val allCategoryForCategorySelection = MutableLiveData<List<AllCategory>>() fun addAllCategoryForCategorySelection(allCategoryValue: List<AllCategory>) { allCategoryForCategorySelection.value = allCategoryValue } // private var _parentCategoryList = MutableLiveData<List<AllCategory>>() private var _parentCategoryList = SingleLiveEvent<List<AllCategory>>() var parentCategoryList = _parentCategoryList fun getCategoryList(categoryParentId: Int) { allCategoryForCategorySelection.value?.let { val categoryList = ArrayList<AllCategory>() it.forEach { allCategory -> if (allCategory.parent == categoryParentId) { categoryList.add(allCategory) } } _parentCategoryList.value = categoryList } } }
解决方案
核心问题分析
返回时观察者重复触发,本质是Fragment重建/恢复时,LiveData会自动重发最新值,即使使用SingleLiveEvent,若ViewModel未重置状态或Fragment再次调用getCategoryList,依然会触发跳转。同时原代码直接拿sharedViewModel.allCategory.value存在数据未就绪的风险,且每次Fragment创建都会重复调用数据加载逻辑。
具体修复步骤
1. 优化ViewModel:增加状态标记避免重复处理
替换SingleLiveEvent为普通MutableLiveData,新增状态变量记录空列表跳转是否已处理,防止重复触发:
@HiltViewModel class CategorySelectionViewModel @Inject constructor(private val resourcesProvider: ResourcesProvider) : ViewModel() { private val allCategoryForCategorySelection = MutableLiveData<List<AllCategory>>() private var _parentCategoryList = MutableLiveData<List<AllCategory>>() val parentCategoryList = _parentCategoryList // 标记当前分类的空列表跳转是否已处理 private var hasHandledEmptyList = false fun addAllCategoryForCategorySelection(allCategoryValue: List<AllCategory>) { allCategoryForCategorySelection.value = allCategoryValue } fun getCategoryList(categoryParentId: Int) { // 每次加载分类时重置状态 hasHandledEmptyList = false allCategoryForCategorySelection.value?.let { allCategory -> // 简化过滤逻辑 val categoryList = allCategory.filter { it.parent == categoryParentId } _parentCategoryList.value = categoryList } } // 标记空列表跳转已完成 fun markEmptyListHandled() { hasHandledEmptyList = true } // 判断是否需要处理空列表跳转 fun shouldHandleEmptyList(): Boolean { return !hasHandledEmptyList } }
2. 修改Fragment逻辑:仅首次加载时处理空列表跳转
优化全局分类数据的监听方式,确保数据就绪后再加载当前分类;同时在观察者中判断是否已处理过空列表跳转:
@AndroidEntryPoint class CategorySelectionFragment : BaseFragment<FragmentCategorySelectionBinding>(FragmentCategorySelectionBinding::inflate) { private val arguments by navArgs<CategorySelectionFragmentArgs>() private val sharedViewModel by activityViewModels<HomeSharedViewModel>() private val categorySelectionViewModel by viewModels<CategorySelectionViewModel>() lateinit var categorySelectionAdapter: CategorySelectionAdapter override fun setupViews(view: View) { categorySelectionAdapter = CategorySelectionAdapter { findNavControllerSafely()?.navigate( MainHostFragmentDirections.actionMainHostFragmentToCategorySelectionFragment(it.id) ) } binding.rvCategorySelection.apply { layoutManager = LinearLayoutManager(context) adapter = categorySelectionAdapter } } override fun setupObservers() { // 先监听全局分类数据,确保数据就绪后再加载当前分类 sharedViewModel.allCategory.observe(viewLifecycleOwner) { allCategory -> categorySelectionViewModel.addAllCategoryForCategorySelection(allCategory) categorySelectionViewModel.getCategoryList(arguments.parentCategoryId) } categorySelectionViewModel.parentCategoryList.observe(viewLifecycleOwner) { categoryList -> if (categoryList.isEmpty()) { // 仅在未处理过的情况下执行跳转 if (categorySelectionViewModel.shouldHandleEmptyList()) { findNavControllerSafely()?.navigate( CategorySelectionFragmentDirections.actionCategorySelectionFragmentToProductListFragment() ) categorySelectionViewModel.markEmptyListHandled() } return@observe } categorySelectionAdapter.submitList(categoryList) } } }
3. 额外优化说明
- 移除了原
setupViews中直接调用getCategoryList的逻辑,改为在全局分类数据就绪后触发,避免无效的数据加载 - 简化了ViewModel中的分类过滤逻辑,使用
filter替代手动遍历
为什么之前的方法无效?
- 移除观察者:Fragment返回时会重新创建观察者,依然会收到LiveData的最新值
- SingleLiveEvent:若ViewModel未重置状态或Fragment再次调用
getCategoryList,SingleLiveEvent依然会发送新值;且SingleLiveEvent本身只能被一个观察者消费,不适合该场景
内容的提问来源于stack exchange,提问作者Pratik Fagadiya
相关产品推荐
相关产品推荐

