Search code examples
androidandroid-fragmentsandroid-architecture-components

Dynamically switch content/fragments in android


My app currently uses a navigation host (Android Architecture Components navigation) to switch between fragments. One of these fragments is a more complex form where I want to show/hide some content depending on whether button 1 or button 2 is pressed.

Here is a sketch of it:

enter image description here

How is that normally done in Android? Would I add a second navhost inside of the (blue background) fragment which itself is shown in a nav host? I am also using data binding, would I then have to use a second binding object for the fragments inside the second navhost or could they share a binding object with the "main" (blue background) fragment?

Or would it be better to just manually inflate the two fragments (the one for button1 and the one for button2)? But then, what would I use as container to host them?


Solution

  • Since you use the Navigation components for the main navigation, from a "clean architecture" point of view, it's desirable to use them for the navigation between the child Fragments as well.

    You can have a NavHostFragment inside a Fragment. The important thing is to not set app:defaultNavHost="true" this time.

    My sample app has a FragmentB with two children FragmentB1 and FragmentB2.

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/childNavHostFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@+id/toggleGroup"
        app:layout_constraintVertical_bias="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:navGraph="@navigation/child_navigation" />
    

    child_navigation.xml

    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/chlld__navigation.xml"
        app:startDestination="@+id/dest_fragment_b1">
    
        <fragment
            android:id="@+id/dest_fragment_b1"
            android:name="com.example.nestednavhostfragment.FragmentB1"
            android:label="FragmentB1" />
        <fragment
            android:id="@+id/dest_fragment_b2"
            android:name="com.example.nestednavhostfragment.FragmentB2"
            android:label="FragmentB2" />
    
        <action android:id="@+id/action_show_b1" app:destination="@+id/dest_fragment_b1"/>
        <action android:id="@+id/action_show_b2" app:destination="@+id/dest_fragment_b2"/>
    </navigation>
    

    With a MaterialButtonToggleGroup in the parent FragmentB, you can navigate using the <action> tags in the above navigation graph:

     <com.google.android.material.button.MaterialButtonToggleGroup
            android:id="@+id/toggleGroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            app:checkedButton="@+id/button1"
            app:singleSelection="true"
            app:layout_constraintBottom_toTopOf="@+id/childNavHostFragment"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.25">
    
            <com.google.android.material.button.MaterialButton
                style="?attr/materialButtonOutlinedStyle"
                android:id="@+id/button1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:checkable="true"
                android:text="Show B1"
                />
            <com.google.android.material.button.MaterialButton
                style="?attr/materialButtonOutlinedStyle"
                android:id="@+id/button2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="Show B2"
             />
        </com.google.android.material.button.MaterialButtonToggleGroup>
    

    FragmentB code, note that you need to "find" the correct NavController using the childNavHostFragment container

    class FragmentB : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            return inflater.inflate(R.layout.fragment_b_layout, container, false)
        }
    
        override fun onViewStateRestored(savedInstanceState: Bundle?) {
            super.onViewStateRestored(savedInstanceState)
            val childNavController  = childNavHostFragment?.findNavController()
            button1.setOnClickListener {childNavController?.navigate(R.id.action_show_b1)}
            button2.setOnClickListener {childNavController?.navigate(R.id.action_show_b2)}
        }
    }