TLDR: looking for the best way to use Navigation C...
# compose-android
k
TLDR: looking for the best way to use Navigation Compose to do something along the lines of a "login flow" - a graph/set of screens separate from all of the authenticated screens. I really like the help provided by the real-world code of the Now In Android sample as well as the JetNews sample, given the former has almost everything one may need, and the latter has a great drawer implementation to go with it. The only thing I'm needing to add which neither of them have is a real-world example of a separate navigation flow - in my case, a few screens to show when the user has not yet authenticated. Can anyone either point to such an example out there or provide some pointers? It's not immediately clear if there should be two separate nav hosts (one called authenticated and one not)... and/or two separate nav controllers... and/or two separate nav graphs... or...?
d
You should use a single nav host with a fixed starting destination and that should ideally be your (authenticated) home screen. This is the ideal approach if you ever want to support deep-linking. Every destination (including your starting destination) could then decide if it cares whether the user is authenticated and if so it can navigate to your login flow (with saveState so that you can later restore the stack but also to make sure pressing back will exit the app). When the user logs in successfully you can then pop your login flow and restore your previous nav stack, leaving the user back where they were.
k
In case it's helpful to add: I was hoping to get some clear answers to these types of questions using https://developer.android.com/jetpack/compose/navigation#nested-nav but that links to https://developer.android.com/guide/navigation/navigation-design-graph#nested_graphs which feels like it describes the XML Navigation Component more so than just using Navigation Compose.
@dorche one NavHost. Got it. Thank you. Do we gain something by having separate NavGraphs, NavControllers, "nested graphs"? For instance, you mentioned "you can then pop your login flow, and restore your previous nav stack". For the following, I think I'm comfortable with navigating (with
popUpTo
) back to
C
, but only if I'm aware of it and its route.
GardenedA -> GardenedB -> GardenedC -> LoginX -> LoginY -> LoginZ
How do I tell it, "pop all the login screens", being completely unaware of what's before them? Make them separate graphs and then pop off an entire graph?
d
One NavController but you should pass callbacks only anyway so that should be abstracted away from pretty much all ui code. NavGraphs can be useful yes, they help with keeping your navigation sane. You could also then scope objects to a particular NavGraph. Could you not pop up to LoginX inclusively instead? Note that these should all be functions passed around so that you could easily swap the implementation (nav component).
k
Could you not pop up to LoginX inclusively instead?
Maybe I'll get this wrong, doing it from memory, but I've mostly seen syntax like
Copy code
navigate("gardened-c-route") {
    popUpTo("gardened-c-route") {
        inclusive = true
    }
}
But in my case, I want to navigate to that route, without knowing that route. I'm guessing I must be missing something?
For instance, if I could say "navigate to LoginX, but also pop LoginX, and therefore actually navigate to the thing before LoginX", that would be awesome, but my understanding is that's not how
popUpTo
works. Maybe I've got it wrong?
@yschimke / @Colton Idle do you have any clarification you might be able to offer?
y
k
Oh, I think I'm starting to understand. If the app is launched with a deep link to screenC, and right before showing that screen we check to see the user is not logged in, then at that point we start the login flow and pass in the navigate action which will pop up to screenC. @dorche is that right?
d
Where exactly you do the check (before showing or after showing etc) is up to you/the way you want your app to behave. Let's take a step back maybe to make this easier to reason about - let's ignore
saveState
and
restoreState
for now: When the app is launched, deep-link or not, you land at either your starting destination or your deep-link destination. If this destination needs the user to be authenticated, it can simply call
.navigate(LoginFlow)
. Your stack then is
MainFlow (long stack here) -> LoginX -> LoginY -> LoginZ
Once LoginZ is done, it can simply call
navController.popBackStack(LoginX, inclusive = true)
which should then pop all the login screens, leaving you back wherever you were on
MainFlow
.
This has some caveats - obviously there are consequences to building the combined stack of
MainFlow
+
LoginFlow
and the fact you're not saving state and restoring it. But I feel like it paints a good starting picture of what we are trying to achieve. Once this is clear you can start thinking about saving and restoring state so that your back stack is even nicer
k
Ok, that's the syntax I think what I was missing
navController.popBackStack(LoginX, inclusive = true)
@dorche is there a good sample somewhere of the proper way to save and restore state?
Which is to say, which problem are we trying to solve when we say we want to save and restore? Process death, for instance? Other...?
d
I don't know of a good example that has all this, sorry 😕 I'm not sure if there's a specific problem we are solving with save and restoreState here tbh - someone else might be able to weight it but the way I see it it's just nicer to have only your
LoginFlow
on your backstack so that pressing the system back button when you are on
LoginX
, backgrounds/kills the app (whatever system does when there's only 1 entry on your back stack and press back)
Without save and restore state, pressing back on
LoginX
by default will bring you back to
GardenedDestination
, which then needs to do something about the fact that the user did not go through the login flow - for example navigating again to the login flow, which then could be a bit annoying for the user 🤷
k
Oh, right... yeah... and now I see how you said all of this yesterday, but I was just slow to catch up. Sorry you had to repeat yourself.
@dorche I really appreciate your help. Am I understanding you correctly that you understand the concepts of why we have this need to save and restore, but you aren't yet comfortable with the how? This feels like a problem that can't be brushed aside.
d
Yeah, mostly down to our designers/UX being "you have to login first" for the apps I've worked on so not had to this "properly" on a real project. It should be a very small change if you have all the actions passed as lambdas tho - I just don't want to suggest code that I am not 100% sure will work, maybe someone else has it and could share.
c
not much to add here except that ian lake always says that the same approach for nav component works the same whether you're using the compose variant of it or the view/fragment based variant this "case study" from ian is always a good refresher. timestamped for you to exact point in the video:

https://www.youtube.com/watch?v=09qjn706ITA&t=290s

k
@Colton Idle that was really good. Thanks for the share. I guess the question becomes, where should this common logic live if we need to create say, 42 screens that all need to verify user is logged in before displaying themselves.
c
Ian I think recommended to me once to create a
composable
navigation type that requires being logged in.
lemme grab that code really quick
So basically instead of having
Copy code
NavHost{
composable(Screen.X.route) { ...
composable(Screen.Y.route) { ...
composable(Screen.Z.route) { ...
}
you do
Copy code
NavHost{
RequireLoggedIn(Screen.X.route) { ...
RequireLoggedIn(Screen.Y.route) { ...
composable(Screen.Z.route) { ...
}
Mine looks sorta like this
Copy code
@Composable
fun RequireLoggedIn(
    navController: NavController,
    appStateHolder: AppStateHolder,
    content: @Composable () -> Unit
) {
I have been lurking on this slack channel for a while and asked my own questions regarding logged in state. Ive formulated some of those thoughts into a blog post thats still in draft. ill publish it one day... lmao
k
You guys are awesome. Thank you so much!
c
I always thought some of this stuff was "too" complicated for such a simple case of logged in/out
but one thing Ian said stuck with me. and basically it was just that. a lot of different apps have very different "rules" when logged in/out. for me. my app is pretty "simple". You can NOT see anything "IN" the app unless you're logged in. but ian gave me good examples where this isn't the case. like twitter for example. you open the app and are taken to login screen. but if you want. you can click a twitter url. and you're taken to the app. and you can see the tweet. but if you try to react to the tweet, then you get a modal login dialog. etc.
basically that everyones use case is slightly different. and so you can dedupe that code typically, but it really depends on your business rules. enforcing those businessness rules at the right layer can make a big difference if in the future you move from a very simple "you need to be logged in to see the app" to a "twitter scenario (above)". and if you dont allow every destination to handle login state itself you could be in for a bad time if you have to refactor from that one scenario to the other.
but yeah. only constant is change. yada yada yada. 😄 cheers