0

I have a BottomNavigationView hooked up to 3 fragments (A, B and C) via the Navigation component. I also have a login fragment, which I am popping off the stack after successful login.

Observed navigation behavior:

A > B > C > A > B > C > B > Back > C > Back > B > Back > A > Back > Exit

Expected behavior:

(after reading comments of @ianhanniballake and Principles of navigation)

A > B > C > A > B > C > B > Back > A > Back > Exit

My problem is similar to circular navigation logic, but I have it in the BottomNavigationView. How to achieve my expected behavior?

main_nav.xml

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_nav"
    app:startDestination="@id/login_fragment">

    <fragment
        android:id="@+id/login_fragment"
        android:name="com.example.app.LoginFragment"
        android:label="Login">

        <action
            android:id="@+id/login_action"
            app:destination="@id/home_fragment"
            app:launchSingleTop="true"
            app:popUpTo="@id/main_nav"
            app:popUpToInclusive="true" />

    </fragment>

    <!-- Fragments A, B and C tied to BottomNavigationView-->

</navigation>

BottomNavigationView setup

navController = findNavController(R.id.main_nav_host)
mainActivityBinding.bottomNavView.setupWithNavController(navController)
Abhimanyu
  • 11,351
  • 7
  • 51
  • 121
  • 2
    As per the [Principles of Navigation](https://developer.android.com/guide/navigation/navigation-principles#fixed_start_destination), you should always exit out of the start destination of your graph. It is hugely disorienting to users to be randomly (in their mind) dumped out of your app from multiple screens in your app. – ianhanniballake Jun 02 '19 at 04:09
  • @ianhanniballake Have edited the question. Bottom navigation apps can exit from any top-level navigation fragment. (Behaviour similar to Google Drive) – Abhimanyu Jun 02 '19 at 04:29
  • Are you navigating only via the bottom nav buttons? Because you should always be going back to A, then out of the app - that's the behavior that `setupWithNavController()` does for you - what you're seeing is the behavior if you actually have a different start destination that you've popped off the stack. The Principles are what all apps, Google apps included, are converting to use. – ianhanniballake Jun 02 '19 at 04:35
  • I have to side with Principles of Navigation on this one. I currently have an app installed that breaks this rule, and it's pretty weird to not be taken back to the starting fragment. As a user, you have to keep a mental log of which apps break the principle, and that's not really ideal. –  Jun 02 '19 at 04:40
  • @ianhanniballake Google Drive was the main reason for my expected behaviour. Upon reading the principles of navigation, I have changed my expected behaviour. Please check the edited question. Thanks for responding. – Abhimanyu Jun 02 '19 at 05:45
  • @Abhi, your expected behavior is the default behavior. I'll update my answer with the full code that works for me. –  Jun 02 '19 at 06:09
  • @glucaio I was successfully able to exit from `HomeFragment` using `finish()` , but still the circular stack problem exists. please check my updated question. – Abhimanyu Jun 02 '19 at 11:30
  • Hmm, I can't think of a reason why that would happen. I would make sure you're using the latest versions of the libraries and then build a bare-bones working model of just the bottom navigation with 3 fragments. The link you posted about circular logic doesn't apply in this case because you don't need actions between the 3 fragments linked to your bottom nav. Also, I'm not seeing how your `loginFragment` fits into the equation. Like I said, strip everything down to just the bottom nav with 3 fragments, and I'm sure you'll see it works as expected fairly easily. –  Jun 02 '19 at 12:35
  • @Abhi, it just dawned on me that you might be navigating between fragments A, B and C by other means than just the bottom nav, and that's what's causing fragments to be retained on your back stack. If that's the case, I apologize if my previous comments added nothing to the discussion. I'm just learning this stuff right now as well. But anyway, if that is indeed what's happening, then calling `popBackStack()` before each `navigate()` call should be sufficient to get your expected behavior. Again, my apologies.. I was clearly needing sleep last night. –  Jun 03 '19 at 02:30
  • @glucaio As per your suggestion, I have made an app with bottom navigation alone and it works as expected. It breaks after adding the login fragment. Kindly check the project and help me : https://github.com/Abhimanyu14/BottomNav – Abhimanyu Jun 03 '19 at 13:42
  • Updated my answer to match your requirements. [Nested Graphs](https://developer.android.com/guide/navigation/navigation-design-graph#nested_graphs) and [First-time user experience](https://developer.android.com/guide/navigation/navigation-conditional#first-time_user_experience) might be helpful as well. –  Jun 04 '19 at 01:13

1 Answers1

1

In this case, you'll want to set up your login flow as a nested graph inside your main navigation graph.

Your start destination should be assigned to one of the 3 fragments hooked up to your BottomNavigationView so that pressing Back takes the user to this screen before exiting the app.

In your HomeFragment (the start destination), you can check if the user is logged in, and navigate to the nested login graph if needed.

HomeFragment.kt

if (!isLoggedIn) {
    val action = HomeFragmentDirections.showLogin()
    findNavController().navigate(action)
} else {
    // show bottom nav
}

You'll have to handle hiding/showing the BottomNavigationView as needed.

Your updated navigation graph will look like this:

main_nav.xml

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_nav"
    app:startDestination="@id/home">

    <fragment
        android:id="@+id/home"
        android:name="com.example.app.HomeFragment"
        android:label="Home">

        <action
            android:id="@+id/show_login"
            app:destination="@id/login_nav"/>

    </fragment>

    <!-- Fragments B and C -->

    <include app:graph="@navigation/login_nav"/>

</navigation>