Can I use multiple NavHostFragments in Navigation Component?

An easy way to create two navHostFragments is to create another navigation.xml file.

In my app for example I have two navHostsFragments.

I defined the first one for navigation flow so when the user enters the app he goes to the login fragment which is the navHostFragment.

After user logs in he is transferred to the mainActivity which contains my new navHostFragment.

That way whenever I start a new activity that contains my new navHostFragment.

Hope this helps


You can create two navigation graphs to achieve the behavior you want. One for the top level destinations and a second one for the modal sheet. They need to be independent and do not have any links between each other. You can't use only one nav graph as the "navigation surface" is a different one. For the main navigation it's the activity and for the modal bottom sheet it's the bottom sheets window (which is in case of a BottomSheetDialogFragment actually a different window).

In theory this can be achieved very easily:

  • main_nav.xml holds Settings, NoteList and Trash
  • filter_nav.xml holds the FilterMenu, Search, and TagList

If you don't want back navigation on the top level you can even do the top level without a navigation controller using fragment transactions.

So basically you need a (BottomSheet)DialogFragment which needs an seperate NavHost independent from the main/other NavHost. You can achieve this with following class:

dialog_fragment_modal_bottom_sheet.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/filterNavHost"/>

ModalBottomSheetDialogFragment .kt

class ModalBottomSheetDialogFragment : BottomSheetDialogFragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
        inflater.inflate(R.layout.dialog_fragment_modal_bottom_sheet, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // We can't inflate the NavHostFragment from XML because it will crash the 2nd time the dialog is opened
        val navHost = NavHostFragment()
        childFragmentManager.beginTransaction().replace(R.id.filterNavHost, navHost).commitNow()
        navHost.navController.setGraph(R.navigation.filter_nav)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return super.onCreateDialog(savedInstanceState).apply {
            // Normally the dialog would close on back press. We override this behaviour and check if we can navigate back
            // If we can't navigate back we return false triggering the default implementation closing the dialog
            setOnKeyListener { _, keyCode, event ->
                if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
                    view?.findNavController()?.popBackStack() == true
                } else {
                    false
                }
            }
        }
    }
}

We do two tricks here:

  1. We need to manually create the NavHost fragment. If we directly put it into XML, it will crash the second time the dialog is opened as the ID is already used

  2. We need to overwrite the dialog's back navigation. A dialog is a separate window on top of your activity, so the Activity's onBackPressed() gets not called. Instead, we add a OnKeyListener and when the back button is released (ACTION_UP) we check with the NavController whether it can pop the back stack (go back) or not. If it can pop the back stack we return true and thus consume the back event. The dialog stays open and the NavController goes one step back. If it is already at the starting point, the dialog will close as we return false.

You can now create a nested graph inside the dialog and not care about the outer graph. To show the dialog with the nested graph use:

val dialog = ModalBottomSheetDialogFragment()
dialog.show(childFragmentManager, "filter-menu")

You could also add the ModalBottomSheetDialogFragment as <dialog> destination in main_nav, I did not test this though. This feature is currently still in alpha and was introduced in navigation 2.1.0-alpha03. Because this is still in alpha, the API might change and I'd personally use the code above to show the dialog. As soon as this is out of alpha/beta, using a destination in main_nav.xml should be the preferred way. The different way to show the dialog makes no difference from a user's perspective.

I create a sample application with your navigation structure here on GitHub. It has working back navigation on both levels with the two independent graphs. You can see it working here on Youtube. I used a bottom bar for the main navigation, but you can replace it with a drawer instead.