14

In the new Navigation architecture component, how to implement conditional navigation?

Currently I have a single activity with LoginFragment and HomeFragment. Based on a certain login_flag, I used to call either fragment from the MainActivity. Since LoginFragment is called only once, I have set the startDestination to HomeFragment and the Navigation loads this HomeFragment. is there any way to check the login_flag before the Navigation loads the HomeFragment.

Shashi Kiran
  • 999
  • 5
  • 13
  • 27

3 Answers3

9

This is how I deal with conditional navigation :

  1. Set HomeFragment as the start destination
  2. Create a global action for LoginFragment

    <action
        android:id="@+id/action_global_loginFragment"
        app:destination="@id/loginFragment"
        app:launchSingleTop="false"
        app:popUpTo="@+id/nav_graph"
        app:popUpToInclusive="true" />
    
  3. Perform conditional navigation inside onViewCreated :

    // HomeFragment.kt
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
        if(!appAuth.isAuthenticated()) {
            view.findNavController().navigate(R.id.action_global_loginFragment)
        }
    }
    
maxbeaudoin
  • 6,546
  • 5
  • 38
  • 53
  • It does the necessary transition, but my login screen still has the back arrow in the action bar. Any idea how to get rid of it? – Aleks N. Mar 22 '19 at 13:38
  • 2
    @AleksN. yes, you need to define multiple "home" destinations so that the navigation component understand that there's nothing to go back to. Add the login screen to the set of destinations. See https://stackoverflow.com/a/53390772/79152. – maxbeaudoin Mar 22 '19 at 20:45
  • 1
    My UI getting stuck when navigating back to home fragment from global fragment using this approach. Can you help me? – Mustufa Ansari Feb 28 '21 at 09:52
  • @MustufaAnsari how do you navigate back? The best way to redirect after login is to pop the LoginFragment like this `navController.popBackStack()`. Also try double-checking/debugging your conditional logic and experimenting with `popUpTo` and `popUpToInclusive`. Since I answered this question, Jetpack evolved quite a bit and although this answer is still relevant, the code might need to be updated with current best practices. For instance, you should use safeargs and Direction classes now. – maxbeaudoin May 31 '21 at 20:51
3

You can add navigation listener inside your activity, and from there navigate to LoginFragment based on your conditions, like this:

findNavController(nav_host_fragment).addOnNavigatedListener { controller, destination ->
        when(destination.id) {
            R.id.HomeFragment-> {
             if(someCondition) {
               //Navigate to LoginFragment
             }

        }
    }
Alex
  • 9,102
  • 3
  • 31
  • 35
  • 1
    There is one problem with this approach. If you call `NavController.navigate(R.id.destinationId)` when your app in background - you'll get an error: `java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState` Is this bug in Navigation Architecture Component? – Alexey Podolian Jun 04 '18 at 12:15
  • Why would you want to call navigate if your app is in background? – Alex Jun 04 '18 at 12:56
  • For example, I run an app on a device but it locked by timeout (or other reason). Then I click `Run app` in Android Studio. Android Studio started an app and then it crashes with this exception. – Alexey Podolian Jun 04 '18 at 13:34
  • 1
    You can also stuck in an endless loop. For example if your condition is loggedIn=false -> then navigation to login fragment. the controller.navigation(...) will trigger addOnNavigatedListener again before navigating to target. – Erkan Jul 25 '20 at 10:06
1

I'd like to add that there is a codelab on developer.android.com for this purpose.

In all required fragments, you define a "next_action" (IDs obviously don't have to be unique) like this:

<action
    android:id="@+id/next_action"
    app:popUpTo="@id/home_dest">
</action>

Then, conditionally, you can set onClickListeners in your code:

view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener(
    Navigation.createNavigateOnClickListener(R.id.next_action, null)
)
cslotty
  • 1,696
  • 20
  • 28