8

I am planning to implement navigation like this:
enter image description here
The problem I face is when user is in LoginFragmennt and presses back button it again loads up LognFragment ie. stuck in loop.

I navigate to LoginnFragment using conditional navigation as per this answer.

How to properly implement this?

Sergei Bubenshchikov
  • 5,275
  • 3
  • 33
  • 60
Yaswant Narayan
  • 1,297
  • 1
  • 15
  • 20
  • You stuck in loop because on pressing back you returning to previous fragment, but because the same condition is still occur(user not logged for example) you navigating to LoginFragment. What is your scenario? where you want to be navigated on pressing back? – Alex Jul 30 '18 at 05:32
  • I want to close app when user presses back button – Yaswant Narayan Jul 30 '18 at 05:49
  • Like in other apps at first time when you encounter login screen and press back button the app closes – Yaswant Narayan Jul 30 '18 at 05:51
  • Try this solution https://stackoverflow.com/a/51589910/1268507 – Alex Jul 30 '18 at 08:33
  • 1
    Official documentation has a post about this problem https://developer.android.com/guide/navigation/navigation-conditional – RonaldPaguay Jun 15 '20 at 07:06

3 Answers3

11

IMHO how I do it in my app is a little cleaner. Just add these settings in the nav graph:

<fragment
    android:id="@+id/profile_dest"
    android:name="com.example.ProfileFragment">
    <action
        android:id="@+id/action_profile_dest_to_login_dest"
        app:destination="@id/login_dest"
        app:popUpTo="@+id/profile_dest"
        app:popUpToInclusive="true" />       
</fragment>

and then navigate to login via

findNavController().navigate(R.id.action_profile_dest_to_login_dest).

popUpTo and popUpToInclusive close ProfileFragment when we navigate to LoginFragment so if the user navigates back, it exits the app.

Carson Holzheimer
  • 2,890
  • 25
  • 36
  • On successful login then how do we get back to the ProfileFragment? Should login_dest also have an action to navigate to profile_dest and we call navigate? – sbearben Jan 31 '19 at 21:01
  • 2
    Yes exactly. Like it is shown in the diagram in the question. This action should also close (remove from backstack) the login screen. – Carson Holzheimer Feb 01 '19 at 01:17
  • So I implemented my navigation this way and it appeared to work, however now when I go to the conditional screen (in my case login) and upon successful login navigate back (lets say to ProfileFragment as in the original post) and rotate the screen my app crashes with the error: `IllegalStateException("unknown destination during restore: id/profile_dest`. Not sure why this would happen. – sbearben Feb 02 '19 at 09:07
  • 1
    What code did you use to navigate back? You should follow the same process with `popUpTo="@+id/login_dest"` and `popUpToInclusive="true"`. Don't use `popBackStack()` – Carson Holzheimer Feb 02 '19 at 12:13
  • You probably already did that. FWIW I haven't seen this issue. Make sure you're on the latest nav version, post another questions with your code so we can have a look, and add an issue to Googles issuetracker if you think it's a bug. – Carson Holzheimer Feb 02 '19 at 12:25
  • Thanks for your help and yes I'm using navigate and not popBackStack() to navigate back. I've posted a separate question about it here: https://stackoverflow.com/questions/54496008/android-navigation-component-java-lang-illegalstateexception-unknown-destinati – sbearben Feb 02 '19 at 18:14
  • This issue is [fixed](https://developer.android.com/jetpack/androidx/releases/navigation#1.0.0-beta01) in 1.0.0-beta01 apparently – Carson Holzheimer Feb 08 '19 at 00:37
6

One of the solutions that i can propose is to override inside your activity onBackPressed method, and finish the activity if your current destination(before on back pressed handled) is login fragment.

override fun onBackPressed() {
    val currentDestination=NavHostFragment.findNavController(nav_host_fragment).currentDestination
    when(currentDestination.id) {
        R.id.loginFragment -> {
            finish()
        }
    }
    super.onBackPressed()
}
Alex
  • 9,102
  • 3
  • 31
  • 35
  • This solution works. Now I have a doubt. For closing a activity I would use `exit(0)`. What is the difference between `exit(0)` and `finish()`? – Yaswant Narayan Jul 30 '18 at 10:46
  • 4
    exit(0) will kill your application, finish() finishing your activity. Don't use exit(0). – Alex Jul 30 '18 at 10:56
2

Here's an official solution suggested by Ian Lake in Navigating navigation video at Jul 23, 2020 on Android Developers YouTube channel. The solution is based on navigation 2.3 release which introduced an ability to return a result to the previous destination.

In our case the login fragment returns LOGIN_SUCCESSFUL state to the previous destination, it might be the profile fragment or any other fragment which requires login.

class LoginFragment : Fragment(R.layout.login) {
    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val navController = findNavController()
        val savedStateHandle = navController.previousBackStackEntry?.savedStateHandle
            ?: throw IllegalStateException("the login fragment must not be a start destination")
            
        savedStateHandle.set(LOGIN_SUCCESSFUL, false)
        // Hook up your UI, ask for login
        
        userRepository.addLoginSuccessListener {
            savedStateHandle.set(LOGIN_SUCCESSFUL, true)
            navController.popBackStack()
        } 
    }
}

The profile fragment subscribes to the LOGIN_SUCCESSFUL state and processes it. Note that the observer lambda won't be called until the login fragment put a result in and return back to the profile fragment.

class ProfileFragment : Fragment(R.layout.profile) {
    ...
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val navController = findNavController()
        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
            userRepository.userFlow.collect { user -> 
                if (user == null) {
                    navController.navigate(R.id.login)
                }
            }
        }
        
        val savedStateHandle = navController.currentBackStackEntry?.savedStateHandle
            ?: throw IllegalStateException()
        
        savedStateHandle.getLiveData<Boolean>(LOGIN_SUCCESSFUL)
            .observe(viewLifecycleOwner) { success -> 
                if (!success) {
                    // do whathever we want, just for an example go to
                    // the start destination which doesn't require login
                    val startDestination = navController.graph.startDestination
                    navController.navigate(startDestination, navOptions {
                        popUpTo(startDestination {
                            inclusive = true
                        })
                    })
                }
            }
    }
}
Valeriy Katkov
  • 33,616
  • 20
  • 100
  • 123