Navigation Architecture Component - Login screen

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.


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()
}

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
                        })
                    })
                }
            }
    }
}