I have a question about conditional navigation. I ...
# android
f
I have a question about conditional navigation. I want to navigate to a login screen when the user is not signed in, but as opposed to the tutorial in the documentation I want to do that from any screen. I couldn't get the SavedStateHandle approach working for my use case. I'll describe my approach in the thread and maybe someone wants to take a look at it and tell me if I'm missing something since it's quite different from the description in the documentation.
👍 1
I keep the user as MutableLiveData in a shared ViewModel and observe that in the activity:
Copy code
viewModel.user.observe(this) {
    if (it == null) {
        val action = LoginDirections.actionGlobalLogin()
        navController.navigate(action)
    }
}
when the user is null (I am logged out) I navigate to the login/signup sub nav graph
To avoid going back to the previous screen without logging in I handle onBackPressed for the login screen manually:
Copy code
override fun onBackPressed() {
    if (navController.currentDestination?.id == R.id.loginFragment) {
        finish()
    } else {
        super.onBackPressed()
    }
}
in both the login and signup fragments I observe the same User in the shared ViewModel as I do in the activity, and just pop the backstack if it changes from null to an actual value ( = we are signed in)
Copy code
userViewModel.user.observe(viewLifecycleOwner) {
    if (it != null) {
        findNavController().popBackStack()
        binding.editTextEmail.clearFocus()
        binding.editTextPassword.clearFocus()
    }
}
i
The tutorial in the docs works from any screen. That's the entire point
f
but do I have to implement it separately for every fragment in the app?
they trigger it when the user navigates to the ProfileFragment
i
If an individual screen requires login, it must check for login and redirect the user to the login if they aren't logged in, yes
We talked about exactly this case and why it is required in the Navigating Navigation video:

https://youtu.be/09qjn706ITA?t=273

f
thanks
so there is no way to set it once in the activity and then forget about it?
i
The video specifically talks about a number of cases that are only handled correctly by following the documentation
f
thanks, I'll watch it carefully
but still, it sounds very verbose to me to implement all this logic for every fragment that needs auth in my app
my approach works on the activity level
but I have to override onBackPressed to handle back for the login and signup screen properly
i
I think you'll find more problems with that approach as you dig deeper around keeping state correctly and interactions with deep links
f
alright, thank you for your help
it's great that Google's team is that responsive
i
Happy to help :-)
f
for me the observer in the current fragment is triggered right away
the one where we pop up to
LiveData gets a value as soon as we call
observe
, right?
It works if I use currentBackStackEntry as the lifecycle owner as described in the documentation. But in the video above you're using viewLifeCycleOwner which triggers right away. Am I missing something?
and the approach from the docs crashes when we rotate the screen with "IllegalStateException: NavController is not available before onCreate()"
I know how to fix the problem with accessing the navController in onCreate, I'm just wondering if I'm doing something wrong because neither the tutorial in the docs nor the one from the video works for me without modifications
i
The video is assuming you're using the latest Fragment 1.3, which fixes the observe issue with the new state manager. Specifically, this bug: https://issuetracker.google.com/issues/161654580
And the docs work fine if you are using
FragmentContainerView
, which has been the recommended approach for quite some time. It sounds like you are still using
<fragment>
f
I'm using FragmentContainerView but maybe I've made a mistake somewhere else
but thanks for the link!
it works now with fragment 1.3.0-beta01 👍
now that all the code is
onViewCreated
it looks like it could make sense to create an extension function for all fragments that need authentication
i
Yeah, if you want to do the same thing in all fragments on a failure to log in, then that makes a lot of sense. That might not be the best user experience though - maybe some screens want to show a full screen overlay saying that login is required instead of kicking the user out
👍 1
f
thank you very much!
The LiveData that triggers the navigation to the login flow in the first place, it is also triggered when you press back while
LOGIN_SUCCESSFUL
is still false. Is this expected behavior? You don't notice it if you immediately move to another destination without an animation, but you can see it if you for example when you finish the activity. In this case, the input fields become empty because you are navigating to the login fragment again for a split second.
This approach from the Codelab handles back in a much simpler way, and it says "last updated October 2020": https://codelabs.developers.google.com/codelabs/advanced-android-kotlin-training-login-navigation#5
here the LoginFragment handles the back button itself (without a
LOGIN_SUCCESSFUL
flag). Is there anything wrong with this approach?