With `nav3` how do you handle "go to login screen ...
# compose
u
With
nav3
how do you handle "go to login screen on logout"? Should I have some sort of on-off
LogOutEvent
which reset the backstack and sets login screen? Or should it better be derived from state, ie.
Flow<User?>
emittnit null? I think I like the stateful way more
Copy code
@Composable
fun AppRoot() {
    val user = userFlow.collectAsStateWithLifecycle().value

    LaunchedEffect(key1 = user) {
        if (user == null) {
            backStack.clear()
            backStack.add(element = LoginKey)
        }
    }
but on activity recreation this obviously clears the backstack again .. so no good unless I keep a flag on each
Screen
type if it's "inside logged in part" or is there a better way? What about
NavDisplay
per app state? (logged in, logged out)?
p
I prefer a State based solution
m
You need to manage the backstack. well i have done it in my app. I am clearing the stack then only adding the next screens after login. Same in logout case i am clearing the stack and adding only login screen. It is working ok
1
u
@malik bilal how do you detect logout happened? do you meam to explicitly clean backstack and set Login screen after logoit susoend finction return? What about when logout happens due to some endpoint 401ing and refresh fails?
m
@ursus yes you need to explicitly clean the backstack. I am using firebase authentication so on logout you will receive callback and on the response you can do the process afterwards
u
so you're doing the event based style one off event comes and you manually wrangle the backstack
dont you worry about ui somehow missing the event? i.e not listening because its in background etc?
👆 1
@Pablichjenkov do you have some input as how to do it?
p
Short on time to write but in general I just clear the user respective data(tokens, user table and such) and then relaunch the App Startup flow Activity.
It could be like closing existing activity and launch your splash screen Activity
Will give more details later
u
oh I thought you meant staying within compose; not restarting the host activity
p
I basically talked from an Android perspective where you can handle Activities "from outside" the graph. But thinking of Compose Multiplatform, where there's no Activities and only nav3 is available. Humm 🧐🤔 looks like you will have to do some tricky stuff
u
I hope basic login logout is not tricky..
p
I wonder, 🤔 if you have a global accessible root-navigator. Then you can let it know, and it will do the backstack transactions/operations. You can use a global event dispatcher, or making your root-navigator a Singleton that you can inject in the navigation graph the logout button lives. Now, speaking of lifecycle, the root-navigator owns the root NavDisplay so it is always in a resumed state I guess. So no reason to worry about receiving events or direct function invokations in the wrong lifecycle state.
u
isnt that basically event based the other guy showed?
p
I would say it depends on the implementation details. The way you communicate between the logout screen and your root-navigation-controllers is up to you. 1- (event based)You can use a global EventBus, the classic publish/subscribe solution to communicate 2 live instances in an App. History proved this doesn't scale well but is an option. 2-(event based) You can have a Singleton UserMamager that exposes the loginStatus in a SharedFlow. 3-(state based) You can have a Singleton UserMamager that exposes the loginStatus in a StateFlow. 4- (direct function call)Your root-navigator is injected in the logout screen and you just invoke a logout() function in it
i
tbh, maybe you're just overthinking things? Your UI can do
Copy code
val backStackToPassToNavDisplay = if (user == null) {
  loginBackStack
} else {
  backStack
}
And then your login code is
Copy code
user = newlyLoggedInUser
// Reset the login back stack to
// be ready for the next login
loginBackStack.clear()
loginBackStack.add(LoginKey)
And your logout code is
Copy code
user = null
// Reset the back stack for when
// the user logs in
backStack.clear()
backStack.add(HomeKey)
Just because NavDisplay takes a single back stack doesn't mean you have to model your state as a single back stack - you can just swap which state you pass your NavDisplay
u
but your samples, that assumes some sort of one off loggedin/logged event? if it was state (flow collected), then on activity recreation it will trigger say the clearing+adding(loginkey) again, clobbering the backstack no?
i
State is for state, not for events. The login code and logout code is for when you mark the user as logged in and mark them as logged out - a login button being pressed, a logout button being pressed, that kind of event
u
that fits for the case when logout is manually initiated via a button, but what about the case whem session (tokens) expires on its own? typically i call som authRepository.logout, which deletes user data, so user becomes null - but this doesnt have a ui I can poke
i
are you under the impression your back stack and login back stack need to live in the UI layer? That is not a requirement either
💡 1
u
hmm, is that a good idea to have backstack reference so deep? Btw basically I can see a simple solution of emitting some LoggedOutEvent which UI receives and does the backstack resetting But I was always worried about events like this, it always seemed to me they can be missed (if UI is aomwhow not listening) Can they?
i
Hoisting state up to the scope that needs to modify it is a super normal thing to do in Compose. That includes hoisting state out of composition
u
I've always struggled with this, in theory it works in my head, but in practise it stops working after a viewmodel, as there is where DI starts to manage stuff. Would you inject the NavBackStack instance to domain objects?