Child fragment menu gets carried over to other parent fragments from BottomNavigationView

169 Views Asked by At

Some context:

  1. I have an app with a single Activity: MainActivity.kt

  2. MainActivity.kt has a BottomNavigationView, which hosts 3 top destination fragments:

  • 1st Fragment: TodayFragment.kt
  • 2nd Fragment: TasksSetsListFragment.kt
  • 3rd Fragment: DaysListFragment.kt
  1. The 1st fragment (TodayFragment.kt) hosts 2 child fragments with a ViewPager
  • 1st child Fragment: TasksListFragment.kt
  • 2nd child Fragment: JEntriesListFragment.kt
  1. Child Fragments TasksListFragment.kt and JEntriesListFragment.kt have their own Appbar menus, since they require different options. And since these child fragments have their menus, their parent fragment (TodayFragment.kt) does not have any menu

  2. The other two top destination fragments (TasksSetsListFragment.kt and DaysListFragment.kt) also have their respective menus.

  3. When swiping between the child Fragments, the menu changes depending on the fragment.

All Good here.

Now the problem:

Lets say I am in the child fragment TasksSetsListFragment.kt inside its parent fragment TodayFragment.kt, as you can see in the picture below:

enter image description here

If I tap on the top destination fragment in the middle (TasksSetsListFragment.kt) for example, the menu from the child fragment TasksSetsListFragment.kt from parent fragment TodayFragment.kt gets carried over to that top destination fragment and mixes itself with that fragment's menu.

enter image description here

As you can see, now I have the search and sort options from the child fragment TasksSetsListFragment.kt and from the top destination fragment TasksSetsListFragment.kt.

In the over flow menu, also the options from the over flow menu from the child fragment were transferred and mixed with the options from the over flow menu from the top destination fragment.

This same behavior occurs with the other top destination fragment DaysListFragment.kt.

Can you help me and let me know what am I doing wrong and teach me how to go about this? I can't find any useful thing on the internet. Or perhaps I am asking the wrong questions on Google.

I appreciate the help.

Here are the relevant files:

TodayFragment.kt

private const val TAG = "TodayFragment"

@ExperimentalCoroutinesApi
@AndroidEntryPoint
class TodayFragment : Fragment(R.layout.fragment_parent_today) {

    private val viewModel: TodayViewModel by viewModels()
    private lateinit var binding: FragmentParentTodayBinding
    private var fabClicked: Boolean = false

    private lateinit var viewPager: ViewPager2

    private val rotateOpen: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.rotate_open_anim) }
    private val rotateClose: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.rotate_close_anim) }
    private val fromBottom: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.from_bottom_anim) }
    private val toBottom: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.to_bottom_anim) }
    private val fadeIn: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.fade_in) }
    private val fadeOut: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out) }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding = FragmentParentTodayBinding.bind(view)
        binding.apply {
            tasksListTransparentWhiteScreen.setOnClickListener {
                fabAnimationsRollBack(binding)
                fabClicked = !fabClicked
            }
        }
        initViewPagerWithTabLayout(binding)
        todayDateDisplay(binding)
        initFabs(binding)
        loadTodayEventCollector()
        getFragmentResultListeners()
    }

    private fun getFragmentResultListeners() {
        setFragmentResultListener("add_edit_request"){_, bundle ->
            val result = bundle.getInt("add_edit_result")
            onFragmentResult(result)
        }
        setFragmentResultListener("create_set_request_2"){_, bundle ->
            val result = bundle.getInt("create_set_result_2")
            onFragmentResult(result)
        }
        setFragmentResultListener("task_added_to_set_request"){_, bundle ->
            val result = bundle.getInt("task_added_to_set_result")
            val message = bundle.getString("task_added_to_set_message")
            onFragmentResult(result, message)
        }
        setFragmentResultListener("task_added_from_set_request"){_, bundle ->
            val result = bundle.getInt("task_added_from_set_result")
            val message = bundle.getString("task_added_from_set_message")
            onFragmentResult(result, message)
        }
    }

    private fun onFragmentResult(result: Int, message: String? = ""){
        viewModel.onFragmentResult(result, message)
    }

    private fun loadTodayEventCollector() {
        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
            viewModel.todayEvent.collect { event ->
                when (event) {
                    is TodayViewModel.TodayEvent.NavigateToAddTaskScreen -> {
                        val action = TodayFragmentDirections
                            .actionTodayFragmentToTaskAddEditFragment(task = null, title = "Add task"
                                , taskinset = null, origin = 1)
                        findNavController().navigate(action)
                    }
                    is TodayViewModel.TodayEvent.ShowTaskSavedConfirmationMessage -> {
                        Snackbar.make(requireView(), event.msg, Snackbar.LENGTH_LONG).show()
                        setViewPagerPage(0)
                    }
                    is TodayViewModel.TodayEvent.ShowTaskSavedInNewOrOldSetConfirmationMessage -> {
                        Snackbar.make(requireView(), event.msg.toString(), Snackbar.LENGTH_LONG).show()
                    }
                    is TodayViewModel.TodayEvent.ShowTaskAddedFromSetConfirmationMessage -> {
                        Snackbar.make(requireView(), event.msg.toString(), Snackbar.LENGTH_LONG).show()
                        fabClicked = true
                        setFabAnimationsAndViewStates(binding)
                        setViewPagerPage(0)
                    }
                    is TodayViewModel.TodayEvent.NavigateToAddTasksFromSetBottomSheet -> {
                        val action = TasksListFragmentDirections
                            .actionGlobalSetBottomSheetDialogFragment(task = null, origin = 2)
                        findNavController().navigate(action)
                    }
                }.exhaustive
            }
        }
    }

    // This will soon be used to be 1
    private fun setViewPagerPage(index: Int){
        viewModel.postActionWithDelay(300, object: TodayViewModel.PostActionListener{
            override fun onDelayFinished() {
                viewPager.setCurrentItem(index, true)
            }
        })
    }

    private fun todayDateDisplay(binding: FragmentParentTodayBinding) {
        binding.apply {
            tasksListDateheader.apply {
                dateHeaderDayofmonth.text = viewModel.getCurrentDayOfMonth()
                dateHeaderMonth.text =  viewModel.getCurrentMonth()
                dateHeaderYear.text = viewModel.getCurrentYear()
                dateHeaderDayofweek.text = viewModel.getCurrentDayOfWeek()
            }
        }
    }

    private fun initViewPagerWithTabLayout(binding: FragmentParentTodayBinding) {
        viewPager = binding.todayViewpager
        val tabLayout: TabLayout = binding.todayTablayout
        viewPager.adapter = activity?.let { TodayPagerAdapter(it) }
            Logger.i(TAG, "initViewPagerWithTabLayout", "viewPager is not null")
            TabLayoutMediator(tabLayout, viewPager) { tab, index ->
                tab.text = when (index) {
                    0 -> "Tasks"
                    1 -> "Journal"
                    else -> throw Resources.NotFoundException("Tab not found at position")
                }.exhaustive
                when (index) {
                    0 -> {

                    }
                    1 -> {
                        fabClicked = false
                    }
                }
            }.attach()
    }

    private fun initFabs(binding: FragmentParentTodayBinding) {
        binding.apply {
            tasksListFab.setOnClickListener {
                onMainFabClick(binding)
            }
            tasksListSubFab1.setOnClickListener {
                Logger.i(TAG, "initFabs", "Coming soon")
            }
            tasksListSubFab2.setOnClickListener {
                viewModel.onAddTasksFromSetClick()
            }
            tasksListSubFab3.setOnClickListener {
                viewModel.onAddNewTaskClick()
            }
        }
    }

    private fun onMainFabClick(binding: FragmentParentTodayBinding) {
        setFabAnimationsAndViewStates(binding)
    }

    private fun setFabAnimationsAndViewStates(binding: FragmentParentTodayBinding) {
        setFabAnimationVisibilityAndClickability(binding, fabClicked)
        fabClicked = !fabClicked
    }

    private fun setFabAnimationVisibilityAndClickability(binding: FragmentParentTodayBinding, clicked: Boolean) {
        if (!clicked) fabAnimationsRollIn(binding) else fabAnimationsRollBack(binding)
    }

    private fun fabAnimationsRollIn(binding: FragmentParentTodayBinding) {
        binding.apply {
            HGDAAnimationUtils.apply {
                HGDAViewStateUtils.apply {
                    setViewAnimation(v1 = tasksListFab, a = rotateOpen)
                    setViewAnimation(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, a = fromBottom)
                    setViewAnimation(v1 = tasksListSubFab1Tv, v2 = tasksListSubFab2Tv, v3 = tasksListSubFab3Tv, a = fromBottom)
                    setViewAnimation(v1 = tasksListTransparentWhiteScreen, a = fadeIn)
                    setViewVisibility(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3
                        , v4 = tasksListSubFab1Tv, v5 = tasksListSubFab2Tv, v6 = tasksListSubFab3Tv, visibility = View.VISIBLE)
                    setViewVisibility(v1 = tasksListTransparentWhiteScreen, visibility = View.VISIBLE)
                    setViewClickState(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, clickable = true)
                    setViewClickState(v1 = tasksListTransparentWhiteScreen, clickable = true)
                }
            }
        }
    }

    private fun fabAnimationsRollBack(binding: FragmentParentTodayBinding) {
        binding.apply {
            HGDAAnimationUtils.apply {
                HGDAViewStateUtils.apply {
                    setViewAnimation(v1 = tasksListFab, a = rotateClose)
                    setViewAnimation(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, a = toBottom)
                    setViewAnimation(v1 = tasksListSubFab1Tv, v2 = tasksListSubFab2Tv, v3 = tasksListSubFab3Tv, a = toBottom)
                    setViewAnimation(v1 = tasksListTransparentWhiteScreen, a = fadeOut)
                    setViewVisibility(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3
                        , v4 = tasksListSubFab1Tv, v5 = tasksListSubFab2Tv, v6 = tasksListSubFab3Tv, visibility = View.INVISIBLE)
                    setViewVisibility(v1 = tasksListTransparentWhiteScreen, visibility = View.INVISIBLE)
                    setViewClickState(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, clickable = false)
                    setViewClickState(v1 = tasksListTransparentWhiteScreen, clickable = false)
                }
            }
        }
    }
}

TasksListFragment.kt

const val TAG = "TasksListFragment"

@ExperimentalCoroutinesApi
@AndroidEntryPoint
class TasksListFragment : Fragment(R.layout.fragment_child_tasks_list), TasksListAdapter.OnItemClickListener {

    private val viewModel: TasksListViewModel by viewModels()
    private lateinit var searchView: SearchView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val binding = FragmentChildTasksListBinding.bind(view)
        val tasksListAdapter = TasksListAdapter(this)

        binding.apply {

            tasksListRecyclerview.layoutTasksListRecyclerview.apply {
                adapter = tasksListAdapter
                layoutManager = LinearLayoutManager(requireContext())
                setHasFixedSize(true)
            }

            ItemTouchHelper(object: ItemTouchHelper.SimpleCallback(0,
                ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT){

                override fun onMove(
                    recyclerView: RecyclerView,
                    viewHolder: RecyclerView.ViewHolder,
                    target: RecyclerView.ViewHolder
                ): Boolean {
                    return false
                }

                override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                    val task = tasksListAdapter.currentList[viewHolder.adapterPosition]
                    viewModel.onTaskSwiped(task)
                }
            }).attachToRecyclerView(tasksListRecyclerview.layoutTasksListRecyclerview)
        }

        loadObservable(binding, tasksListAdapter)
        loadTasksEventCollector()
        loadMenu()
    }

    private fun loadObservable(binding: FragmentChildTasksListBinding, tasksListAdapter: TasksListAdapter) {
        viewModel.tasks.observe(viewLifecycleOwner){ tasksList ->
            binding.apply {
                HGDAViewStateUtils.apply {
                    if (tasksList.isEmpty()) {
                        setViewVisibility(tasksListRecyclerview.layoutTasksListRecyclerview, visibility = View.INVISIBLE)
                        setViewVisibility(tasksListLayoutNoData.layoutNoDataLinearlayout, visibility = View.VISIBLE)
                    } else {
                        setViewVisibility(tasksListRecyclerview.layoutTasksListRecyclerview, visibility = View.VISIBLE)
                        setViewVisibility(tasksListLayoutNoData.layoutNoDataLinearlayout, visibility = View.INVISIBLE)
                        tasksListAdapter.submitList(tasksList)
                    }
                }
            }
        }
    }

    private fun loadTasksEventCollector() {
        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
            viewModel.tasksEvent.collect { event ->
                when (event) {
                    is TasksListViewModel.TaskEvent.ShowUndoDeleteTaskMessage -> {
                        Snackbar
                            .make(requireView(), "Task deleted", Snackbar.LENGTH_LONG)
                            .setAction("UNDO"){
                                viewModel.onUndoDeleteClick(event.task)
                            }
                            .show()
                    }
                    is TasksListViewModel.TaskEvent.NavigateToEditTaskScreen -> {
                        val action = TodayFragmentDirections
                            .actionTodayFragmentToTaskAddEditFragment(task = event.task, title = "Edit task", taskinset = null, origin = 1)
                        findNavController().navigate(action)
                    }
                    is TasksListViewModel.TaskEvent.NavigateToAddTaskToSetBottomSheet -> {
                        val action = TasksListFragmentDirections.actionGlobalSetBottomSheetDialogFragment(task = event.task, origin = 1)
                        findNavController().navigate(action)
                    }
                    is TasksListViewModel.TaskEvent.NavigateToDeleteAllCompletedScreen -> {
                        val action = TasksListFragmentDirections
                            .actionGlobalTasksDeleteAllDialogFragment(origin = 1)
                        findNavController().navigate(action)
                    }
                    is TasksListViewModel.TaskEvent.NavigateToDeleteAllScreen -> {
                        val action = TasksListFragmentDirections
                            .actionGlobalTasksDeleteAllDialogFragment(origin = 3)
                        findNavController().navigate(action)
                    }
                }.exhaustive
            }
        }
    }

    private fun loadMenu(){

        val menuHost: MenuHost = requireActivity()
        menuHost.addMenuProvider(object: MenuProvider {

            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {

                menuInflater.inflate(R.menu.menu_tasks_list_fragment, menu)

                val searchItem = menu.findItem(R.id.tasks_list_menu_search)
                searchView = searchItem.actionView as SearchView

                val pendingQuery = viewModel.searchQuery.value
                if (pendingQuery != null && pendingQuery.isNotEmpty()) {
                    searchItem.expandActionView()
                    searchView.setQuery(pendingQuery, false)
                }

                searchView.OnQueryTextChanged{ searchQuery ->
                    viewModel.searchQuery.value = searchQuery
                }

                viewLifecycleOwner.lifecycleScope.launchWhenStarted {
                    menu.findItem(R.id.tasks_list_menu_hide_completed).isChecked =
                        viewModel.preferencesFlow.first().hideCompleted
                }
            }

            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                return when (menuItem.itemId) {
                    R.id.tasks_list_menu_sort_by_date -> {
                        viewModel.onSortOrderSelected(SortOrder.BY_DATE)
                        true
                    }
                    R.id.tasks_list_menu_sort_by_name -> {
                        viewModel.onSortOrderSelected(SortOrder.BY_NAME)
                        true
                    }
                    R.id.tasks_list_menu_hide_completed -> {
                        menuItem.isChecked = !menuItem.isChecked
                        viewModel.onHideCompletedSelected(menuItem.isChecked)
                        true
                    }
                    R.id.tasks_list_menu_delete_completed -> {
                        viewModel.onDeleteAllCompletedClick()
                        true
                    }
                    R.id.tasks_list_menu_delete_all -> {
                        viewModel.onDeleteAllClick()
                        true
                    }
                    else -> false
                }
            }
        }, viewLifecycleOwner, Lifecycle.State.RESUMED)
    }

    override fun onItemClick(task: Task) {
        viewModel.onTaskSelected(task)
    }

    override fun onItemLongClick(task: Task) {
        viewModel.onTaskLongSelected(task)
    }

    override fun onCheckboxClick(task: Task, isChecked: Boolean) {
        viewModel.onTaskCheckedChanged(task, isChecked)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        searchView.setOnQueryTextListener(null)
    }
}

TaskSetsListFragment.kt

@ExperimentalCoroutinesApi
@AndroidEntryPoint
class TaskSetsListFragment : Fragment(R.layout.fragment_tasks_set), TaskSetsListAdapter.OnItemClickListener{

    private val viewModel: TasksSetsListViewModel by viewModels()

    private lateinit var searchView: SearchView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        startShimmerView()
        val binding = FragmentTasksSetBinding.bind(view)
        val tasksSetListAdapter = TaskSetsListAdapter(this)

        binding.apply {
            tasksSetRecyclerview.apply {
                tasksSetRecyclerview.layoutTasksListRecyclerview.apply {
                    adapter = tasksSetListAdapter
                    layoutManager = LinearLayoutManager(requireContext())
                    setHasFixedSize(true)
                }
            }

            tasksSetFab.setOnClickListener {
                val action = CreateTaskSetDialogFragmentDirections.actionGlobalCreateTaskSetDialogFragment(task = null, origin = 1)
                findNavController().navigate(action)
            }

            ItemTouchHelper(object: ItemTouchHelper.SimpleCallback(0,
                ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT){
                override fun onMove(
                    recyclerView: RecyclerView,
                    viewHolder: RecyclerView.ViewHolder,
                    target: RecyclerView.ViewHolder
                ): Boolean {
                    return false
                }

                override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                    val set = tasksSetListAdapter.currentList[viewHolder.adapterPosition]
                    viewModel.onSetSwiped(set)
                }
            }).attachToRecyclerView(tasksSetRecyclerview.layoutTasksListRecyclerview)
        }

        viewModel.taskSets.observe(viewLifecycleOwner) { taskSetsList ->
            binding.apply {
                HGDAViewStateUtils.apply {
                    if (taskSetsList.isEmpty()) {
                        stopShimmerView()
                        setViewVisibility(tasksSetRecyclerview.layoutTasksListRecyclerview, visibility = View.INVISIBLE)
                        setViewVisibility(tasksSetLayoutNoData.layoutNoDataLinearlayout, visibility = View.VISIBLE)
                        setViewVisibility(tasksSetLayoutNoData.layoutNoDataImageview, visibility = View.GONE)
                        tasksSetLayoutNoData.layoutNoDataTextview.text = getString(R.string.you_don_t_have_any_sets)
                    } else {
                        stopShimmerView()
                        setViewVisibility(tasksSetRecyclerview.layoutTasksListRecyclerview, visibility = View.VISIBLE)
                        setViewVisibility(tasksSetLayoutNoData.layoutNoDataLinearlayout, visibility = View.INVISIBLE)
                        tasksSetListAdapter.submitList(taskSetsList)
                    }
                }
            }
        }

        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
            viewModel.taskSetEvent.collect{ event ->
                when (event) {
                    is TasksSetsListViewModel.TaskSetEvent.NavigateToDeleteAllSetsScreen -> {
                        val action = TaskSetsListFragmentDirections
                            .actionGlobalTasksDeleteAllDialogFragment(origin = 2)
                        findNavController().navigate(action)
                    }
                    is TasksSetsListViewModel.TaskSetEvent.NavigateToEditTaskSet -> {
                        val action = TaskSetsListFragmentDirections
                            .actionTaskSetsListFragmentToTasksSetEditListFragment(settitle = event.taskSet.title)
                        findNavController().navigate(action)
                    }
                    is TasksSetsListViewModel.TaskSetEvent.ShowUndoDeleteSetMessage -> {
                        Snackbar
                            .make(requireView(), "Set deleted", Snackbar.LENGTH_LONG)
                            .setAction("UNDO"){
                                viewModel.onUndoDeleteClick(event.taskSet, event.tasksInSetList)
                            }
                            .show()
                    }
                }.exhaustive
            }
        }

        loadMenu()
    }

    private fun loadMenu() {

        val menuHost: MenuHost = requireActivity()

        menuHost.addMenuProvider(object: MenuProvider{

            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {

                menuInflater.inflate(R.menu.menu_tasks_set_fragment, menu)

                val searchItem = menu.findItem(R.id.task_set_action_search)
                searchView = searchItem.actionView as SearchView

                val pendingQuery = viewModel.searchQuery.value
                if (pendingQuery.isNotEmpty()) {
                    searchItem.expandActionView()
                    searchView.setQuery(pendingQuery, false)
                }

                searchView.OnQueryTextChanged { searchQuery ->

                    viewModel.searchQuery.value = searchQuery
                }
            }

            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                return when (menuItem.itemId) {
                    R.id.task_set_action_delete_all_sets -> {
                        viewModel.onDeleteAllSetsClick()
                        true
                    }
                    else -> false
                }
            }
        }, viewLifecycleOwner, Lifecycle.State.RESUMED)
    }

    private fun startShimmerView() { HGDAAnimationUtils.startShimmerView(requireActivity(), R.id.task_set_shimmerframelayout) }

    private fun stopShimmerView() { HGDAAnimationUtils.stopShimmerView(requireActivity(), R.id.task_set_shimmerframelayout) }

    override fun onItemClick(taskSet: TaskSet) {
        viewModel.onTaskSetSelected(taskSet)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        searchView.setOnQueryTextListener(null)
    }
}
0

There are 0 best solutions below