Android navigation graph with navviewholder fragment navigation issue when navigating using list click item

95 Views Asked by At

I have a very simple use case that I am surprised isn't working.

I have a bottom view navigation controller with 2 fragments, a list fragment and a detailed fragment. When I navigate to the detail fragment page I have no problems navigating back.

When I navigate to the detail fragment using explicitly using an item click handler in a recyclerview I can no longer navigate back to the first tab, the list tab using the bottom navigation.

Here is my nav graph

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/job_nav_graph"
    app:startDestination="@id/jobsListFragment">

    <fragment
        android:id="@+id/jobsListFragment"
        android:name="com.plcoding.posterpalfeature.ui.fragments.JobsListFragment"
        android:label="JobsListFragment" >
        <action
            android:name="listToDetail"
            android:id="@+id/action_jobsListFragment_to_jobDetailFragment"
            app:destination="@id/jobDetailFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right">
        </action>
    </fragment>
    <fragment
        android:id="@+id/jobDetailFragment"
        android:name="com.plcoding.posterpalfeature.ui.fragments.JobDetailFragment"
        android:label="JobDetailFragment" >
        <action
            android:id="@+id/action_jobDetailFragment_to_jobsListFragment"
            app:destination="@id/jobsListFragment"
            app:enterAnim="@anim/slide_in_left"
            app:exitAnim="@anim/slide_out_right"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
    </fragment>
</navigation>

main activity

<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.plcoding.posterpalfeature.ui.activity.MainActivity">

    <FrameLayout
        android:id="@+id/flFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <fragment
            android:id="@+id/navHostFragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/job_nav_graph" />


    </FrameLayout>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        app:menu="@menu/bottom_navigation_menu"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

JobDetails

<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/txt_job_details"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Fragment"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Joblist

<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_jobs_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

item callback that doesn't allow me to use bottomnavigation to get back after using to navigate back to joblist after using to navigate to job details

private fun setupItemClickListener() {
        jobsAdapter.setOnItemClickListener {
            val action = JobsListFragmentDirections.actionJobsListFragmentToJobDetailFragment().setJobId(it.job.job_id.toInt())
            findNavController().navigate(action)
        }
    }

JobListFragment

androidx.navigation.fragment.findNavController
//import com.plcoding.multipleroomtables.databinding.FragmentJobListBinding
import com.plcoding.posterpalfeature.api.Resource
import com.plcoding.posterpalfeature.repo.JobsRepository
import com.plcoding.posterpalfeature.ui.activity.MainActivity
import com.plcoding.posterpalfeature.ui.MainViewModel
import com.plcoding.posterpalfeature.ui.adapters.JobAdapter
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
class JobsListFragment : Fragment(R.layout.fragment_job_list){

    companion object {
        const val BUNDLE_KEY = "job_id"
        const val TAG = "JobsListFragment"
    }

    @Inject
    lateinit var repo : JobsRepository
    lateinit var viewModel: MainViewModel
    lateinit var binding: FragmentJobListBinding
    lateinit var jobsAdapter : JobAdapter

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding = FragmentJobListBinding.bind(requireView())
        viewModel = (activity as MainActivity).mainViewModel
        jobsAdapter = JobAdapter()
        setupRecyclerView(binding.rvJobsList)
        setupItemClickListener()
        listenToApiLiveData()
        viewModel.getJobsList()
    }

    private fun setupItemClickListener() {
        jobsAdapter.setOnItemClickListener {
            val action = JobsListFragmentDirections.actionJobsListFragmentToJobDetailFragment().setJobId(it.job.job_id.toInt())
            findNavController().navigate(action)
        }
    }

    private fun listenToApiLiveData() {
        viewModel.jobsListUpdate.observe(viewLifecycleOwner, Observer { response ->
            when(response) {
                is Resource.Success -> {
                    hideProgressBar()
                    hideErrorMessage()
                    response.data?.let { jobsResponse ->
                        jobsAdapter.differ.submitList(jobsResponse.jobs)
                        jobsAdapter.notifyDataSetChanged()
                    }

                }
                is Resource.Error -> {
                    hideProgressBar()
                    response.message?.let { message ->
                        Toast.makeText(activity, "An error occured fetching jobs: $message", Toast.LENGTH_LONG).show()
                    }
                }
                is Resource.Loading -> {
                    showProgressBar()
                }
        }
        } )
    }

    private fun showProgressBar() {
       // paginationProgressBar.visibility = View.VISIBLE
       // isLoading = true
    }

    private fun hideErrorMessage() {

    }

    private fun hideProgressBar() {

    }

    private fun setupRecyclerView(recyclerView: RecyclerView) {
        activity?.lifecycleScope?.launch {
            val jobslist = repo.getAllJobs()
            recyclerView.apply {
                adapter = jobsAdapter
                layoutManager = LinearLayoutManager(activity)
               //x addItemDecoration(DividerItemDecoration(this.context, DividerItemDecoration.VERTICAL))
            }
            jobsAdapter.differ.submitList(jobslist)
        }
    }


}

JobDetailFragment

package com.plcoding.posterpalfeature.ui.fragments

import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import com.plcoding.multipleroomtables.R
import com.plcoding.multipleroomtables.databinding.FragmentJobDetailBinding
//import com.plcoding.multipleroomtables.databinding.FragmentJobDetailBinding
import com.plcoding.posterpalfeature.repo.relations.jobworkerposter.JobPW
import com.plcoding.posterpalfeature.ui.activity.MainActivity
import com.plcoding.posterpalfeature.ui.MainViewModel

class JobDetailFragment: Fragment(R.layout.fragment_job_detail) {

    companion object {
        const val TAG = "JobDetailFragment"
    }
    val args:  JobDetailFragmentArgs by navArgs()

    lateinit var binding: FragmentJobDetailBinding
    lateinit var viewModel: MainViewModel
    lateinit var jobPW: JobPW
    var job_id: Int = -1

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding = FragmentJobDetailBinding.bind(requireView())
        job_id = args.jobId
        viewModel = (activity as MainActivity).mainViewModel
        jobPW = viewModel.getJobById(job_id.toLong())
        jobPW.job
        jobPW.posters
        jobPW.workers
        Log.d(TAG, "job_Id by sage args args.jobId ${job_id}, posters size ${jobPW.posters.size} " +
                " workers size ${jobPW.workers.size} ")
    }
}

This is very simple and should be working.

Any ideas of what to try next?

1

There are 1 best solutions below

0
On

So the issue was with the backstack. I had to add popUp & popUpInclusive to the XML navigation fragment

 <action
            android:id="@+id/action_jobsListFragment_to_jobDetailFragment"
            app:destination="@id/jobDetailFragment"
            app:popUpTo="@id/jobsListFragment"
            app:popUpToInclusive="true" />

This really was not clear from documentation and codelabs this should be default behaviour with BottomNavigationView. If someone can comment on how the popUp attributes work would be very much appreciated. Thank you for your support in the comments :)