Home > Net >  MainActivity cannot be cast to interface error when implementing interface from child fragment in pa
MainActivity cannot be cast to interface error when implementing interface from child fragment in pa

Time:12-04

I have this app in which I have a parent fragment, which has 2 child fragments in a ViewPager2 object.

One of the child fragments has an interface to communicate changes on its menu to the parent fragment.

The child fragment in question is TasksListFragment.kt and the parent fragment is TodayFragment.kt

When I try to initialize the interface in the child fragment onAttach() function, I get

FATAL EXCEPTION: main                                                                                                    Process: com.rajchenbergstudios.hoygenda, PID: 
java.lang.ClassCastException: com.rajchenbergstudios.hoygenda.ui.activity.MainActivity cannot be cast to com.rajchenbergstudios.hoygenda.ui.todaylists.taskslist.TasksListFragment$ChildFragmentListener

I don't understand why I get this error, and it says MainActivity, when the parent is a fragment which is the one implementing the interface in the first place, not the MainActivity.

I have everything set up correctly:

  1. I have an interface in the child fragment
  2. The interface is used on the child fragment onCreateMenu to pass the menu object to its interface function onFragmentMenuChanged(menu: Menu)
  3. I override the child fragment's onAttach() and initialize the interface:
override fun onAttach(context: Context) {
        super.onAttach(context)
        childFragmentListener = context as ChildFragmentListener
    }
  1. I write a function called setListener() which is called from the parent fragment to pass its context this to the function parameter which assigns it to the childFragment listener
fun setListener(listener: ChildFragmentListener) {
        this.childFragmentListener = listener
    }
  1. The parent fragment implements the child fragment listener as seen in the TodayFragment.kt file

Can you tell me what am I doing wrong or how to implement an interface to effectively communicate from child fragment back to its parent fragment?

TasksListFragment.kt

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

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

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

    /**
     * TasksListViewModel.TaskEvent.ShowUndoDeleteTaskMessage: Stays in this class. It asks for components relevant to this class.
     * TasksListViewModel.TaskEvent.NavigateToEditTaskScreen: Stays in this class. The method it overrides comes from task list adapter.
     * TasksListViewModel.TaskEvent.NavigateToDeleteAllCompletedScreen: Stays in this class. Relevant to menu which is in this class.
     * TasksListViewModel.TaskEvent.NavigateToDeleteAllScreen: Stays in this class. Relevant to menu which is in this class.
     */
    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) {

                childFragmentListener.onFragmentChanged(menu)
                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)
    }

    interface ChildFragmentListener {
        fun onFragmentChanged(menu: Menu)
    }

    fun setListener(listener: ChildFragmentListener) {
        this.childFragmentListener = listener
    }

    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 onAttach(context: Context) {
        super.onAttach(context)
        childFragmentListener = context as ChildFragmentListener
    }

    override fun onPause() {
        super.onPause()
        Logger.i(TAG, "onPause", "TasksListFragment paused")
    }

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

TodayFragment.kt

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

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

    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
            }
        }
        setChildFragmentMenus()
        initViewPagerWithTabLayout(binding)
        todayDateDisplay(binding)
        initFabs(binding)
        loadTodayEventCollector()
        getFragmentResultListeners()
    }

    private fun setChildFragmentMenus(){
        val tasksListFragment = TasksListFragment()
        tasksListFragment.setListener(this)
        Logger.i(TAG, "setChildFragmentMenus", "TasksListFragment menu set")
    }

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

    /**
     * TodayViewModel.TodayEvent.NavigateToAddTaskScreen: Relevant to this class. Belongs to Fab which are all in this class.
     * TodayViewModel.TodayEvent.ShowTaskSavedConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
     * TodayViewModel.TodayEvent.ShowTaskSavedInNewOrOldSetConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
     * TodayViewModel.TodayEvent.ShowTaskAddedFromSetConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
     * TodayViewModel.TodayEvent.NavigateToAddTasksFromSetBottomSheet: Relevant to this class. Belongs to Fab which are all in this class.
     */
    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)
                }
            }
        }
    }

    override fun onFragmentChanged(menu: Menu) {
        tasksListMenu = menu
    }

    override fun onPause() {
        super.onPause()
        tasksListMenu.clear()
    }
}

CodePudding user response:

Fragment is not a Context i.e fragment is not a child of context . So when you try to cast context as ChildFragmentListener you are actually casting your Activity to ChildFragmentListener which is giving you this RuntimeException . to make it work you can use childFragmentListener = parentFragment as ChildFragmentListener

Also if your Doing this you do not setListener method anymore.

On other hand i would suggest you do not use listeners to communicate b/w fragments . I see you already using viewModel so just use a shared one to communicate . You can get a shared ViewModel inside child by creating it with parentFragment.

  • Related