Search code examples
kotlinandroid-livedatamutablelivedata

LiveData observer triggered again on fragment back navigation


I am building an Android app that displays a list of categories using a RecyclerView in a Fragment. When the user taps on a category, I navigate to the same Fragment with the child categories of the selected category. If the selected category has no child categories, I navigate to a different Fragment that displays the products in the selected category.

The problem is that when the category list is initially empty (i.e., there are no child categories for the selected category), I navigate to the product list Fragment as expected. However, when I navigate back to the category selection Fragment, the LiveData observer is triggered again, and the empty list is displayed again.

I have tried removing the observer when I navigate to the product list Fragment, but this did not solve the problem. I have also tried using SingleLiveEvent to emit a value only once, but it did not work either.

I want to display the category selection Fragment again if the selected category has child categories, and navigate to the product list Fragment if there are no child categories. How can I prevent the LiveData observer from being triggered again when I navigate back to the category selection 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) {

        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)
            }
        }

    }


}

And Here is ViewModel

@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

        }
    }


}

Solution

  • I haven't tried this, but can you pop the current fragment right before navigating? The idea being, you are not going to use the current fragment so you don't want it to remain in the stack as you move forward to the next fragment.

        categorySelectionViewModel.parentCategoryList.observe(viewLifecycleOwner) {
            val = list.orEmpty()
            if (list.isEmpty()) {
                findNavControllerSafely()?.apply {
                    popBackStack()
                    navigate(
                        CategorySelectionFragmentDirections.actionCategorySelectionFragmentToProductListFragment()
                    )
                }
                return@observe
            }
    
            categorySelectionAdapter.submitList(list)
        }