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

返回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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 11:20:02