Home > Software design >  How can I use Navigation (NavHost) in nested fragments?
How can I use Navigation (NavHost) in nested fragments?

Time:11-30

I'm trying to open Fragment2 from Fragment1(TransformFragment) throught click item in RecyclerView. I tried to use Navigation (NavHost) to solve this problem.

Fragment1 code as below:

class TransformFragment : Fragment() {

    private lateinit var transformViewModel: TransformViewModel
    private var _binding: FragmentTransformBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding
        get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        transformViewModel = ViewModelProvider(this)[TransformViewModel::class.java]
        _binding = FragmentTransformBinding.inflate(inflater, container, false)
        val root: View = binding.root

        val recyclerView = binding.recyclerviewTransform

        val adapter = TransformAdapter()
        recyclerView.adapter = adapter
        transformViewModel.texts.observe(viewLifecycleOwner) { adapter.submitList(it) }

        return root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    class TransformAdapter() :
        ListAdapter<String, TransformViewHolder>(
            object : DiffUtil.ItemCallback<String>() {

                override fun areItemsTheSame(oldItem: String, newItem: String): Boolean =
                    oldItem == newItem

                override fun areContentsTheSame(oldItem: String, newItem: String): Boolean =
                    oldItem == newItem
            }
        ) {

        private val drawables =
            listOf(
                R.drawable.avatar_1,
                R.drawable.avatar_2,
                R.drawable.avatar_3,
                R.drawable.avatar_4,
                R.drawable.avatar_5,
                R.drawable.avatar_6,
                R.drawable.avatar_7,
                R.drawable.avatar_8,
                R.drawable.avatar_9,
                R.drawable.avatar_10,
                R.drawable.avatar_11,
                R.drawable.avatar_12,
                R.drawable.avatar_13,
                R.drawable.avatar_14,
                R.drawable.avatar_15,
                R.drawable.avatar_16,
                R.drawable.avatar_17,
            )

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransformViewHolder {
            val binding = ItemTransformBinding.inflate(LayoutInflater.from(parent.context))
            return TransformViewHolder(binding)
        }

        override fun onBindViewHolder(holder: TransformViewHolder, position: Int) {
            holder.textView.text = getItem(position)
            holder.imageView.setImageDrawable(
                ResourcesCompat.getDrawable(holder.imageView.resources, drawables[position], null)
            )

            holder.itemView.setOnClickListener {

                // my navigation(NavHost) code

            }
        }
    }

    class TransformViewHolder(binding: ItemTransformBinding) :
        RecyclerView.ViewHolder(binding.root) {

        val imageView: ImageView = binding.imageViewItemTransform
        val textView: TextView = binding.textViewItemTransform
    }
}

I tried to use this:

val navController = findNavController(R.id.nav_host_fragment_content_main)

navController.navigate(R.id.nav_detail)

nav_detail to navigate Fragment2, and My Fragment Container. This's my XML. I know that to display Dynamiclly Fragments, you must use FrameLayout, but it produces errors and the fragment container is OK.

    <LinearLayout 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"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:showIn="@layout/app_bar_main">

    <fragment
        android:id="@ id/nav_host_fragment_content_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginLeft="@dimen/fragment_horizontal_margin"
        android:layout_marginRight="@dimen/fragment_horizontal_margin"
        android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/mobile_navigation" />

    <!--
    <FrameLayout
        android:id="@ id/nav_host_fragment_content_main"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginLeft="@dimen/fragment_horizontal_margin"
        android:layout_marginRight="@dimen/fragment_horizontal_margin"
        android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/mobile_navigation" />
        -->

    
</LinearLayout>

CodePudding user response:

I've found the correct answer in Android developers documentation.

holder.itemView.setOnClickListener {

            it.findNavController().navigate(R.id.nav_detail)

        }

CodePudding user response:

To use findNavController() to navigate between fragments, you've to make sure that you've setup with activity properly first.

So, in your activity layout first add container, parent need nod to be FrameLayout itself, I'm gonna make use of ConstraintLayout below.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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=".MainActivity">

<!--    We declare attribute id to hook with MainActivity.java class-->
<!--    Attribute name will change behavior of simple fragment to NavHostFragment-->
<!--    Keep width and height to match_parent so that all our fragments take whole space-->
<!--    We change this NavHost to behave default NavHost-->
<!--    Attach our app navigation graph to this NavHostFragment-->
    <fragment
        android:id="@ id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now let's hook container as an acting host for activity.

MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // NavigationController to set default NavHost as nav_host_fragment.
        navController = Navigation.findNavController(this, R.id.nav_host_fragment)
        NavigationUI.setupActionBarWithNavController(this, navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp()
    }
}

onSupportNavigateUp function helps you to navigate back to previous fragment on action trigered or called specifically on any view with navigateUp().

Now you want to navigate from Fragment1 -> Fragment2.

  1. Make sure you've added two fragments to navigation graph.
  2. Connect first fragment to second with action.
  3. Keep track of destination Id's to call correct one.
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@ id/nav_graph"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@ id/firstFragment"
        android:name=".FirstFragment"
        android:label="FirstFragment"
        tools:layout="@layout/fragment_first">
        <action
            android:id="@ id/action_first_fragment_to_second_fragment"
            app:destination="@id/secondFragment" />
    </fragment>

    <fragment
        android:id="@ id/secondFragment"
        android:name=".SecondFragment"
        android:label="Authors"
        tools:layout="@layout/fragment_second" />

</navigation>

Now finally once action is triggered call action with id like below on controller.

yourListItemView.setOnClickListener {
    findNavController(R.id.action_first_fragment_to_second_fragment)
}

That's it.

Please check the documentation, it has a good information.

  • Related