Search code examples
javaandroidkotlinandroid-architecture-navigation

Navigation Architecture Component - Login screen


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?


Solution

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