Search code examples
androidnavigationnavigation-drawerandroid-navigation-graph

Android Navigation - how to go two destinations deep?


I have an app that manages Boxes. This app has a single activity and uses fragments as destinations. It has (among others) the following destinations/fragments:

  • Home (registered as topLevelDestination)
  • BoxesManagement (registered as topLevelDestination)
  • BoxEdit

From the home destination I want to offer a convenient link to create a new box. For that I want to link BoxEdit directly, but with BoxesManagement in the back stack.

Attempt 1 - Explicit Deep Linking

Since an intermediate destination will only land on the back stack when it is the start destination of a <navigation> element, I placed BoxesManagement (as start) and BoxEdit in a nested graph. The nested graph is in a separate file, but <include>d in the parent graph. Then I used:

findNavController()
    .createDeepLink()
    .setDestination(R.id.nav_edit_box)
    .createPendingIntent()
    .send()

This did work in the way that it got me to the desired destination with BoxesManagement in the back stack. However, it causes two issues:

  • The application is started again. At least that is what I assume happens, since the entire screen flashes very briefly. This behavior lets me assume that I am not supposed to be using deep links for in-app navigation. (This is not surprising as the documentation never hints at this use-case.)
  • When I go back to BoxesManagement, I see the hamburger menu icon (as expected). But when I use the drawer menu to navigate to Home, nothing happens.

Attempt 2 - Direct Navigation

findNavController()
    .navigate(R.id.nav_box_edit)

This does not work and simply fails with:

java.lang.IllegalArgumentException: Navigation action/destination <APP-PKG>:id/nav_box_edit cannot be found from the current destination Destination(<APP-PKG>:id/nav_home) label=Home class=<APP-PKG>.ui.HomeFragment 

I am not surprised, as Murat Yener from Google already pointed out that this is not supported.

Attempt 2b - Setting the Graph

From this question and answer I got the idea to set the graph on the nav controller before navigating.

findNavController().apply {
    setGraph(R.navigation.nav_graph_boxes)
    navigate(R.id.nav_box_edit)
}

This does work, but also causes two issues:

  • When following this navigation, the nav-controller's graph is now R.navigation.nav_graph_boxes instead of R.navigation.nav_graph. When I use a different path with single steps through BoxesManagement, I can also reach BoxEdit but with R.navigation.nav_graph as the controller's graph.
  • The same issue as above with deep linking, navigating to Home using the drawer menu does nothing. I believe this is actually a direct consequence of the first issue.

The proposed solution in the linked Q&A is to set the graph back to R.navigation.nav_graph when navigating back. I don't know where in the code to do that though, as I don't navigate back through any explicit action, but rather through the up-button and the drawer menu, which - as stated before - does not work anymore with this approach.

Attempt 3 - Implicit Deep Linking

As suggested by the official doc I decided to try implicit deep linking, even though that brings none of that sweet type-safety that I was promised when using the navigation component.

I added

<deepLink app:uri="android-app://my.app.url/boxes/{boxId}/edit"/>

to the nav_box_edit fragment definition and used

findNavController()
    .navigate(Uri.parse("android-app://my.app.url/boxes/0/edit"))

from the Home destination.

This got me to my destination, but again I have a problem with this:

  • It does not put BoxesManagement into the back stack (this is to be expected per the documentation).

Question

How do I properly navigate to a (nested) destination while putting another destination on the back stack before it?


Solution

  • Navigating two Destinations Deep

    In order to create a back stack as desired, you just have to push all desired destinations on the stack:

    findNavController().apply {
        navigate(R.id.nav_boxes_management)
        navigate(R.id.nav_box_edit)
    }
    

    This works only if all destinations are in the same nav graph. As specified in the question, this is not the case here, so a slightly different approach needs to be taken:

    findNavController().apply {
        navigate(FragmentHomeDirections.actionBoxesManagement())
        navigate(FragmentBoxesManagementDirections.actionEditBox(0L))
    }
    

    Using the directions of the now-top-of-the-stack destination BoxesManagement, the navigation into the nested graph succeeds.

    (Thanks to ianhanniballake for giving me the crucial hint!)

    Navigating back to Home

    This works, but I don't know why:

    findNavController().apply {
        navigate(FragmentHomeDirections.actionBoxesManagement(), navOptions {
            popUpTo(R.id.nav_home) {
                inclusive = false
                saveState = true
            }
        })
        navigate(FragmentBoxesManagementDirections.actionEditBox(0L))
    }
    

    I figured it out by looking at the source code of NavigationUI, which effectively handles the drawer menu actions.