Hey friends, I wrote a post on compose navigation....
# compose
t
Hey friends, I wrote a post on compose navigation. This post details some of the common mistakes you can make when trying to implement jetpack navigation with compose, and how to avoid them https://programminghard.dev/compose-navigation/
šŸ‘ 3
s
Hey nice article! One thing I was wondering about, with the final approach we fix the problem of hitting back and going back to the login screen which is super bad. But since weā€™re navigating from the ā€œhomeā€ destination to the ā€œloginā€ in the
NotLoggedIn
case, we can then press the back button on the ā€œloginā€ screen right? What will this do? As I see it it will pop the back stack, then go to ā€œhomeā€, and home will quickly realize that weā€™re still not logged it and go to the ā€œloginā€ screen again, making the back gesture basically do a quick flicker of going back and forth or even if this is too quick to flicker it will at least do nothing. Am I missing something here?
t
Hey, thanks for reading. Great question - yes, youā€™re 100% correct. Thereā€™s a back-press navigation loop in this example, which Iā€™ve overlooked! I will update the post. The way I address this problem, is by implementing a
BackHandler
on the login screen, like so:
Copy code
@Composable
fun LoginScreen (onExitApp: () -> Unit) {
    BackHandler(enabled = true) {
        onExitApp()
    }
}
Where
onExitApp()
propagates the event up the call hierarchy, back to the host Activity, which then calls
finish()
s
This is an option for sure, but Iā€™m pretty sure we donā€™t want to actually finish the activity when pressing back. In fact ever since Android 12+ pressing back means that the app goes in the background without finishing the activity, meaning that when open the app again youā€™re in a hot state, skipping showing stuff like the Splash screen and so on. Read more here specifically the quote ā€œRoot launcher activities are no longer finished on Back pressā€. I think itā€™d be a shame to lose this nice feature if we finish the activity ourselves. Do the official docs provide some guidance regarding this exact scenario that Iā€™ve missed maybe? Sorry for the ping @Ian Lake but Iā€™d really appreciate if you share your thoughts or post an appropriate docs link regarding this here šŸ™Œ
t
Another really interesting point, not one Iā€™d given much consideration to. Just having a look at the docs. When onBackPressed is called: https://developer.android.com/reference/android/app/Activity#onBackPressed()
ā€¢ On platform versions prior to
Build.VERSION_CODES.S
, it finishes the current activity, but you can override this to do whatever you want.
ā€¢ Starting with platform version
Build.VERSION_CODES.S
, for activities that are the root activity of the task and also declare an
IntentFilter
with
Intent#ACTION_MAIN
and
Intent#CATEGORY_LAUNCHER
in the manifest, the current activity and its task will be moved to the back of the activity stack instead of being finished. Other activities will simply be finished.
So, we could follow the same pattern, and call
moveTaskToBack()
, instead of `finish()`: https://developer.android.com/reference/android/app/Activity#moveTaskToBack(boolean)
s
Hmm yeah one could do that, but then maybe weā€™d be breaking how things should actually function for APIs 11 and lower? Or in the case where weā€™re not actually on our Launcher activity, where even in 12+ going back does in fact finish the activity? Not sure tbh how Iā€™d solve this perfectly šŸ˜…
t
If you wanted to exactly match the behaviour, you could just follow the docs as above, and
finish()
below Android S, and
moveTaskToBack()
above. If youā€™re not on the launcher activity, I guess you can pass the
nonRoot = true
flag into
moveTaskToBack(nonRoot: ..)
?
nonRoot
boolean: If false then this only works if the activity is the root of a task; if true it will work for any activity in a task.
s
Yeah just read that too. Does that then mean that it will simply not do anything, or that it will finish instead of moving the task to the background?
t
It will ā€˜work for any activityā€™ - work presumably meaning ā€˜will move the task to the backā€™,
Not sure though - I have not tested this!
s
Plus this then puts the burden (and the room to make a mistake) on the developer. And if you refactor stuff, move activities and such it may break without you noticing if you move that code from a Lanucher to a non Launcher activity. Iā€™d love to be able to avoid having to this myself. But yeah, it seems like provided some testing thereā€™s a way to do all this manually as well.
t
Yeah, Iā€™m not sure what alternative you have really. You canā€™t exit the app via the
navController
, and going back to the start destination is not valid here potentially problematic. You could store some state on a
ViewModel
to indicate that the user has failed to log in, and then render some different content on the ā€˜homeā€™ screen to reflect this.
So, going back to the example in the blog post, you might store a flag in the
savedStateHandle
of the
AppViewModel
called, say,
loginSuccessful
. Initially itā€™s null (neither success nor failure). Upon navigating back from the login screen, you set
loginSuccessful = false
. Then, this becomes an additional
ViewState
, say
LoginFailed
. If this is your
ViewState
, then you might render some other content to the user - like a login button and an explanation that the user needs to log in
This way, the user is on the start destination, and if they again press back, they exit the app - relying on the system to decide whether to finish or moveTaskToBack
s
Yeah just because Iā€™d prefer not to handle it myself doesnā€™t mean I know a way to do so šŸ˜… Thatā€™s why I pinged Ian in case he had a better idea. But other than that maybe doing this and adjusting depending on the Android version youā€™re in might make the most sense right now. Or what you suggested right above^^ I havenā€™t found myself in such a situation so I donā€™t have any more ideas atm.
t
Storing a
loginSuccessful
flag is something Ian alludes to in the ā€˜navigating navigationā€™ video actually. I reckon doing this, and rendering some content to the user explaining that they need to login - and then relying on the system to handle exiting the app - this seems like the right way to do it.
Thanks for mentioning this @Stylianos Gakis! I appreciate the discussion. I'll update the post with some new recommendations!
s
Thanks for the discussion! Looking forward to reading the updated article when you find the time to do so šŸ¤—