bryankeltonadams
06/18/2023, 10:15 PM@Composable
fun RequireLoggedIn(
navController: NavController,
appState: FishbowlAppState,
content: @Composable () -> Unit
) {
if (appState.isLoggedIn) {
content()
} else {
if (navController.currentBackStackEntry?.destination?.parent?.route != unauthenticatedRoutePattern) {
navController.navigate(unauthenticatedRoutePattern)
}
}
}
that extra if block inside is basically preventing the navigation loop from happening if the backStackEntry destination parent is already on the unauthenticatedRoutePattern, but it's unfortunate that this is even getting triggered at all, since it's a Composable that's wrapped around a authenticatedRoute screen...
Another thing I had to do to prevent an infinite loop when pressing the back button from the login screen (with the restricted screen on the backstack still) was to override the backHandler in the login screen like this
val onBackPressedCallback = remember {
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Close the app
(context as? Activity)?.finishAffinity()
}
}
}
// Register the onBackPressedCallback
BackHandler {
onBackPressedCallback.handleOnBackPressed()
}
even in the Navigating Navigation video where you say to simply popBackStack in order to get to the startDestination once authenticated, you don't really cover preventing this issue, is the backHandler in the loginScreen the right approach?
lastly here's my logic for onLoginSuccess, which I thought was pretty robust until I was getting the issue where RequireLoggedIn was getting triggered and putting another authenticatedGraph on the backStack (even though I was launching single top) and with a launchedEffect.
onLoginSuccess = {
// Extra protection in case the login is performed twice somehow.
//
// A good sign that this method is working is that we should be able
// to have our start destination as the authenticatedGraph or the unauthenticatedGraph,
// and the app behavior is exactly the same.
//
// This statement is made without taking into consideration future features such
// as deep linking or remembering the last screen the user was on before logging out.
if (!appState.isLoggedIn) {
appState.isLoggedIn = true
val previousDestination = navController.previousBackStackEntry?.destination
previousDestination?.route?.let { route ->
// Navigate to the previous destination
// This is safer compared to popBackStack() since it will not
// throw an exception if the back stack is empty
// or allow the user to double tap the login button and have it pop
// the back stack twice
navController.navigateToRouteAndPopUpToGraph(route)
} ?: navController.navigateToAuthenticatedGraph()
// ^ Navigate to the authenticatedGraph without a specific route
// This would be called if login was to happen and there was no previous route
// to navigate back to, i.e. if the backStack was cleared when logging out
// or if the startDestination was set to the unauthenticatedGraph
}
})
This whole process of converting over from having my start destination being the unauthenticatedGraph to the authenticatedGraph but with a wrapper that redirects to tha unauthenticatedGraph has been pretty frustrating. Nothing in my app besides the unauthenticatedGraph, which contains the loginScreen and the account creation screen should be accessible without being logged in. and I wanted to update my implementation to what you suggested in Navigating Navigation in order to better support deep linking in the future etc..
Also, now that I've been pondering on this for a few days, I'm wondering if it would just be better to have two separate navHosts and to catch this at the main App composable level or something.Tim Malseed
06/18/2023, 10:50 PMbryankeltonadams
06/19/2023, 2:19 AMStylianos Gakis
06/19/2023, 8:09 AMStylianos Gakis
06/19/2023, 8:17 AMHomeScreen
. But now if weâve deep linked into some other screen, you need to again check for login status on that screen too. And this blows up on checking on every single place in the app. What are you doing instead of that? Could this check not be moved top level, outside of any destination? Probably where the NavHostController is first constructed?Tim Malseed
06/19/2023, 10:21 AMTim Malseed
06/19/2023, 10:22 AMTim Malseed
06/19/2023, 10:22 AMTim Malseed
06/19/2023, 10:31 AM@Composable
fun RootComposable {
if (isLoggedIn) {
MainNavHost()
} else {
AuthNavHost()
}
}
Since only one nav host will ever be rendered at any one timeTim Malseed
06/19/2023, 10:32 AMStylianos Gakis
06/19/2023, 10:32 AMStylianos Gakis
06/19/2023, 10:33 AMTim Malseed
06/19/2023, 10:33 AMTim Malseed
06/19/2023, 10:39 AMTim Malseed
06/19/2023, 10:39 AMTim Malseed
06/19/2023, 10:41 AMStylianos Gakis
06/19/2023, 10:43 AMIf you were to âhoistâ the conditional navigation logic away from one of your destination screens, like you suggested before, youâre also going to run into issues with deep linking.How so? What scenario are you thinking about here? Trying to understand better myself too tbh, since I am sure I havenât thought of everything obviously.
Tim Malseed
06/19/2023, 10:49 AMTim Malseed
06/19/2023, 10:50 AMTim Malseed
06/19/2023, 10:50 AMStylianos Gakis
06/19/2023, 11:03 AMYouâd be intercepting this at some higher-level, before arriving at the destination, and taking the user elsewhere, then youâve lost track of where they intended to go.Not necessarily. NavHost handles the deep link immediately as it comes from the activityâs intent. So this will happen asap. In the meantime, youâre probably launching a coroutine on the Activity lifecycle scope, and you do a repeatOnLifecycle(Started) in which you are doing this check for the Auth status. So, so far weâve navigated to the deep link successfully, but now are about to receive the fact that we are logged out. We get that status that weâre logged out in this async manner, and we can then pop the entire backstack, saving the state as we do so, and navigate to the login screen. In there, if you press back, you can exit the app normally, since youâre at the top of the backstack. As you finish logging in, you can now navigate back to the start destination, while popping the login screen from the backstack, and while also
restoreState = true
.
This should bring you back to where you were before, and now you are also logged in.
Now that async check if youâre logged in just returns true and you donât do anything with it.
For that brief moment where youâve deep linked somewhere, but youâve not yet checked the auth status, you can do something like use the splash screen APIs with setKeepOnScreenCondition
to not hide it until youâve gotten some response from it, be it logged in, or logged out.
Wouldnât something like this fix more or less your concerns about losing your deeplinked destination? And no need to do a reusable thing for all app screens either.
I havenât tested this entire setup myself either, since unfortunately Iâm still fighting to remove some legacy Activities which handle login now, but this is toward where Iâd try to steer my implementation. Weâve been using this state restoration for bottom navigation items and it seems to work very well, so I donât see why it wouldnât work for this scenario too.Tim Malseed
06/19/2023, 11:07 AMbryankeltonadams
06/19/2023, 3:12 PMbryankeltonadams
06/19/2023, 3:29 PM