Some context:
I have an app with a single Activity: MainActivity.kt
MainActivity.kt has a BottomNavigationView, which hosts 3 top destination fragments:
- 1st Fragment: TodayFragment.kt
- 2nd Fragment: TasksSetsListFragment.kt
- 3rd Fragment: DaysListFragment.kt
- The 1st fragment (TodayFragment.kt) hosts 2 child fragments with a ViewPager
- 1st child Fragment: TasksListFragment.kt
- 2nd child Fragment: JEntriesListFragment.kt
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
The other two top destination fragments (TasksSetsListFragment.kt and DaysListFragment.kt) also have their respective menus.
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:
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.
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)
}
}

