François ROSTKER
François ROSTKER

Reputation: 191

How to add NavHostFragment in a Fragment destination?

My goal is to understand and use the Navigation component in the best way with a complex navigation architecture (I suppose).

To make the global context, I use a main BottomNavigationBar with 5 items. For the management of the fragments associate, I use the example given by Google : https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample.

But my case is a bit more complex than just a bottom bar. In one of the destinations from one of the fragments launched by the bottom bar, I have another RecyclerView menu and a fragment container to navigate between fragments. I put here the important part of my primary 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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    app:startDestination="@id/mainFragment">
    <fragment
        android:id="@+id/mainFragment"
        android:name="com.exemple.fragment.MainFragment"
        android:label="@string/main_label"
        tools:layout="@layout/mainFragment">
        <action
            android:id="@+id/action_mainFragment_to_goScreen"
            app:destination="@id/goScreen">
        </action>
    </fragment>
    <fragment
        android:id="@+id/goScreen"
        android:name="com.exemple.fragment.GoFragment"
        android:label="@string/goLabel"
        tools:layout="@layout/fragment_go">
        <action
            android:id="@+id/action_goScreen_to_mainFragment"
            app:destination="@id/mainFragment" />
        <argument
            android:name="arg1"
            app:argType="com.exemple.customType"
            android:defaultValue="@null"
            app:nullable="true" />
        <argument
            android:name="arg2"
            app:argType="string"
            android:defaultValue="@null"
            app:nullable="true" />
    </fragment>
</navigation>

And the xml with the bottom bar which starts this graph :

<?xml version="1.0" encoding="utf-8"?>
<layout 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"
    tools:context="com.exemple.MainActivity">

    <RelativeLayout
        android:id="@+id/constraintLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/fake_toolbar"
            android:layout_width="0dp"
            android:layout_height="0dp"/>

        <androidx.fragment.app.FragmentTabHost
            android:id="@+id/main_host_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/bottom_navigation" />

        <!-- navigation bottom -->
        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="start"
            android:layout_alignParentBottom="true"
            android:background="@color/navigationBarColor"
            app:itemIconTint="@color/textColorNavigationBar"
            app:itemTextColor="@color/textColorNavigationBar"
            app:menu="@menu/menu_bottom_navigation" />
    </RelativeLayout>
</layout>

And here is an example of the graph corresponding to the go fragment (the destination ):

<?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/go"
    app:startDestination="@id/firstGoFragment">

    <action android:id="@+id/action_to_firstGoFragment"
        app:destination="@id/firstGoFragment"/>
    <fragment
        android:id="@+id/firstGoFragment"
        android:name="com.exemple.go.firstGoFragment"
        android:label="@string/firstGoFragmentLabel"
        tools:layout="@layout/firstGoFragment" >
    </fragment>

    <action android:id="@+id/action_to_secondFragment"
    app:destination="@id/secondFragment"/>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.exemple.go.SecondFragment"
        android:label="@string/secondFragmentLabel"
        tools:layout="@layout/secondFragment" >
    </fragment>
</navigation>

The associate xml is :

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorGreyLight"
        android:clickable="true"
        android:focusable="true">

        <RelativeLayout
            android:id="@+id/linearLayout3"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/quit"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginStart="16dp"
                android:paddingBottom="15dp"
                android:paddingEnd="15dp"
                android:paddingTop="15dp"
                app:srcCompat="@drawable/ic_quit_white" />

            <com.teedji.mobeelity.custom.CustomTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="@string/go"
                android:textColor="@color/colorWhite"
                android:textSize="17sp"
                android:textStyle="bold" />

        </RelativeLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/goMenu"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@color/colorWhite"
            android:overScrollMode="never"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/linearLayout3" />

        <!-- include fragment child container -->
        <fragment
            android:id="@+id/go_nav_host_fragment_container"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginTop="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/goMenu"
            app:navGraph="@navigation/go"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Now, to navigate to the go screen I simply use

findNavController().navigate(MainFragmentDirections.actionMainFragmentToGoScreen(arg1, arg2))

or

findNavController().navigate(R.id. action_mainFragment_to_goScreen)

But when I am on the Go fragment, the navController the I find using findNavController() is still the mainFragment one. So the navigate() fonction didn't find the action id. To solve it, I had to change the navHost programaticaly in the GoFragment like this :

override fun onCreate(savedInstanceState: Bundle?) {
        Log.i(LOG_TAG, "onCreate")
        super.onCreate(savedInstanceState)
        if( fragmentManager != null ) {
            // If the Nav Host fragment exists, return it
            val existingFragment = fragmentManager!!.findFragmentByTag(FRAGMENT_TAG) as NavHostFragment?
            existingFragment?.let { navHostGoFragment = it }

            // Otherwise, create it and return it.
            if ( navHostGoFragment == null) {
                navHostGoFragment = NavHostFragment.create(R.navigation.go)
                fragmentManager!!.beginTransaction()
                        .add(R.id.go_nav_host_fragment_container, navHostGoFragment!!, FRAGMENT_TAG)
                        .commit()
            }
        }else {
            Log.e(LOG_TAG, "fragmentManager is null so I can't find the fragment host")
        }
}

and then use navHostGoFragment!!.navController.navigate(R.id.action_to_firstGoFragment) to navigate in the new container But doing like this, my firstFragment is created twice (I can see it on my logs) and that generate some problems like Cannot add the same observer with different lifecycles for example ( I solve this with adding

if  (model.getLiveData().hasObservers()) {
        model.getLiveData().removeObserver(observer)
    }

before

model.getLiveData().observe(viewLifecycleOwner, observer)

) But I think I am not doing the Navigation part like I should...

Could you provide any help to make this work more efficiently with stability ?

Upvotes: 4

Views: 8293

Answers (1)

Daniel Gunna
Daniel Gunna

Reputation: 327

Try to use go_nav_host_fragment_container.findNavController().navigate(), if you are using Kotlin Android Extensions or findViewById(R.id.go_nav_host_fragment_container).findNavController().navigate(), and see if it works.

Upvotes: 1

Related Questions