14

Please note that I am not talking about Nested Navigation Graphs here. I'm specifically talking about using NavHostFragments as subsections of other NavHostFragments and using the NavigationComponent to transition back and forth between the sub and parent fragments. That being said...

How do we properly use Navigation Component with NavHostFragments inside other NavHostFragments?

For example: Let's say I have a MainActivity with the following layout:

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">
    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            style="@style/Toolbar"/>
    </android.support.design.widget.AppBarLayout>
    <fragment
        android:id="@+id/navHost"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />
</LinearLayout>

Main Activity has a NavHostFragment with the id navHost that uses a navigation graph nav_graph, which looks like the following:

<?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/fragmentA">
    <fragment
        android:id="@+id/fragmentA"
        android:name="com.example.nestednavfragmentstest.FragmentA"
        android:label="FragmentA"
        tools:layout="@layout/fragment_a">
        <action android:id="@+id/action_fragmentA_to_fragmentB" app:destination="@id/fragmentB"/>
    </fragment>
    <fragment
        android:id="@+id/fragmentB"
        android:name="com.example.nestednavfragmentstest.FragmentB"
        android:label="FragmentB"
        tools:layout="@layout/fragment_b"/>
</navigation>

Essentially, navHost can navigate from FragmentA to FragmentB (on say, a button click). But wait, there's more...

What if I would like Fragment B to contain it's own inner NavHostFragment? The xml might look something like this:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Fragment B"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <fragment
        android:id="@+id/embeddedNavHostFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="50dp"
        app:defaultNavHost="true"
        app:layout_constraintTop_toBottomOf="@id/textView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:navGraph="@navigation/inner_nav_graph"/>
</android.support.constraint.ConstraintLayout>

As you can see, FragmentB has its own NavHostFragment called embeddedNavHostFragment, which has a navGraph that looks as follows:

<?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/inner_nav_graph"
    app:startDestination="@id/innerFragmentB1">
    <fragment
        android:id="@+id/innerFragmentB1"
        android:name="com.example.nestednavfragmentstest.InnerFragmentB1"
        android:label="InnerFragmentB1"
        tools:layout="@layout/inner_fragment_b1">
        <action android:id="@+id/action_innerFragmentB1_to_innerFragmentB2" app:destination="@id/innerFragmentB2"/>
    </fragment>
    <fragment
        android:id="@+id/innerFragmentB2"
        android:name="com.example.nestednavfragmentstest.InnerFragmentB2"
        android:label="InnerFragmentB2"
        tools:layout="@layout/inner_fragment_b2"/>
</navigation>

As you can see, this inner NavHostFragment is used to navigate between two inner fragments InnerFragmentB1 and InnerFragmentB2. Now for the main question...

What is the proper way override onSupportNavigateUp() in the MainActivity?

Originally, I did something like this:

override fun onSupportNavigateUp(): Boolean {
    return findNavController(R.id.navHost).navigateUp()
}

But in certain cases, this would cause a crash due to back stack inconsistencies. For example:

  1. MainActivity starts with Fragment A in navHost.
  2. Navigate via action_fragmentA_to_fragmentB to B (which starts with B1 within embeddedNavHost)
  3. Navigate via action_innerFragmentB1_to_innerFragmentB2 from B1 to B2 (occurs on embeddedNavHost)
  4. Press Up (embeddedNavHost returns from B2 to B1 [desired outcome])
  5. Press Up (navHost returns from B to A [desired outcome])
  6. Navigate again via action_fragmentA_to_fragmentB to B
  7. IllegalArgumentException: navigation destination id/action_fragmentA_to_fragmentB [not desired outcome]

So in the end, I ended up doing:

override fun onSupportNavigateUp(): Boolean {
    val hostedFragment = navHost?.childFragmentManager?.primaryNavigationFragment
    return when(hostedFragment){
        is FragmentB -> {
            if(hostedFragment.isInnerFragmentB2Showing()){
                findNavController(R.id.embeddedNavHostFragment).navigateUp()
            } else {
                findNavController(R.id.navHost).navigateUp()
            }
        }
        is FragmentA -> {
            findNavController(R.id.navHost).navigateUp()
        }
        else -> false
    }
}

This ended up working. There was no crash. But, I'm just curious as to if there's a more proper/automated way handle the up navigation of embedded NavHostFragments. This current way feels more like a workaround than a proper solution. Are there any other alternatives to how to get this to work?

Also, I'm curious of people's opinions of NavHostFragments being embedded within each other. Is it an appropriate use? Figuring out how to handle the back button accordingly was not an easy or straight forward solution as there was no documentation on embedding NavHostFragments inside one another and navigating back & forth between parent and sub fragments. So, I'm wondering if the reason why there is no documentation is because maybe it's an improper (or unexpected) use of NavigationComponent and therefore not recommended.

If you'd like to take a look at my sample project, please go here

JHowzer
  • 3,684
  • 4
  • 30
  • 36
  • Also posted a related question in the Issue Tracker [here](https://issuetracker.google.com/issues/120690961) – JHowzer Dec 09 '18 at 19:58

0 Answers0