I have a launched effect that doesn't seem to trig...
# compose
c
I have a launched effect that doesn't seem to trigger a nav event like 0.1% of the time during app startup. My thought is that it's just some race condition, but if anyone is familiar with compose-nav and launchedEffects wants to take a look at my scenario I'd appreciate it. Short sample code in thread
Essentially I have my AppRouter (compose nav host) at the root of my project, but I also have an artifical screen for branding (not my choice. my product team forced me to build it). I think my issue happens because of the way I implemented the artificial splash screen.
Copy code
Box(modifier = Modifier.fillMaxSize()) {
  Box(modifier = Modifier.align(Alignment.Center)) {
    AppRouter(navController)
  }
  // This is my splash screen
  AnimatedVisibility(
      vm.appState.showSplashScreen,
      enter = EnterTransition.None,
      exit = fadeOut()) {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center) { ArtificialSplashScreen() }
  }
}
Inside of my app router, I do a check around most screens to see if the user is logged out. If so then I navigate to the login screen via a launchedEffect.
Copy code
composable(Screen.Home.route) {
  RequireSignedInUser(navController) {
    HomeScreen()
  }
}
Copy code
@Composable
fun RequireSignedInUser(
    navController: NavController,
    content: @Composable () -> Unit
) {

  val user by appState.currentUser.collectAsState()

  if (user == null) {
    LaunchedEffect(Unit) { navController.navigate(route = Screen.Login.route) }
This works 99.9% of the time but it seems like sometimes the launchedEffect isn't triggered (either that or the navigate call is failing somehow?). I can repro this... just supppppper infrequently but I do have users complaining. This only seems to happen on fresh installs (user not logged in) Any thoughts?
a
if (user != null)
is that condition expected? Navigate to the login if the
user
is not null?
c
Whoops sorry. Copy pasta error. That should say if (user == null). Updating now
a
That looks right, so I’m wondering how
currentUser
gets updated. What is the default value, and does it end up being
null
in the case where the expected navigate doesn’t happen? For a race condition like this, my instinct is that
currentUser
is updated multiple times in short succession, and you can’t rely on any intermediate values being received and acted upon by the
LaunchedEffect
c
currentUser is a stateFlow.
Copy code
val currentUser: StateFlow<AppUser?>
One thing to add that I should've said already, is that the user check if it fails... it loads the content(). But the content never loads. It's just a blank screen. Hence why I think it does make it to the launchedEffect, but it just doesn't get called, or navigate doesn't get called?
Copy code
if (user == null) {
    LaunchedEffect(Unit) { navController.navigate(route = Screen.Login.route) }
} else {
  content()
}
and you can’t rely on any intermediate values being received and acted upon by the
LaunchedEffect
This statement definitely has me questioning my implementation. hm...
Makes no sense how I install the app and it's literally just blank. I wish I could force a LaunchEffect to be called or something.
a
could force a LaunchEffect to be called
The keys for your
LaunchedEffect
would be the way to restart the effect, if the keys change. So the current key of
Unit
implies that it’d only call
navigate
the first time the
user
is
null
and that composable is present. So maybe some other place is calling
navigate
right after this effect navigates to
Login
? You might want to log all of your navigate calls, and see if the order and which ones are happening are what you’d expect.
c
Yeah. What's difficult is that this doesn't repro easily. Thanks for the sanity check though @Alex Vanyo looks like there is something wonky there.
I wonder if I should just add the initial value. hm
Copy code
.collectAsState(initial = null)
this is a really bad time to be using logcatv2. it just doesn't show logs on app launch. lol 😭
Maybe its because I'm routing to a destination in a nested nav graph 🤔
Finally able to repro with additional logging
Copy code
16:22:25.521 5362-5362/? E/ME: RequireSignedInUser recomposed
16:22:25.521 5362-5362/? E/ME: nav to sign up
16:22:25.581 5362-5362/? E/ME: RequireSignedInUser recomposed
16:22:25.581 5362-5362/? E/ME: nav to sign up
16:22:25.748 5362-5362/? E/ME: RequireSignedInUser recomposed
16:22:25.748 5362-5362/? E/ME: nav to sign up
16:22:25.910 5362-5362/? E/ME: RequireSignedInUser recomposed
16:22:25.910 5362-5362/? E/ME: nav to sign up
16:22:25.946 5362-5362/? E/ME: RequireSignedInUser recomposed
16:22:25.946 5362-5362/? E/ME: nav to sign up
16:22:26.276 5362-5362/? E/ME: RequireSignedInUser recomposed
16:22:26.276 5362-5362/? E/ME: nav to sign up
looks like it tried to nav 6 times in a second. getting closer...
to force it to repro I tried to just put a repeat {} block inside of my
Copy code
fun RequireSignedInUser(
with no luck. still doesn't' repro easily.
ALright. so it only seems to be happening in release builds... the story continues
Update: It seems that if I add some opacity to my background color of my splash screen then the issue goes away? I wonder if this is somehow related to https://issuetracker.google.com/issues/192993290
j
cc @Doris Liu
r
I’m curious as to why the navigation is wrapped in a
LaunchedEffect
.
navigate
is not a suspend and there’s no reason to delay the navigation until recomposition is completed.
c
I think it's because I only want it to be called once?
r
Once the log in completes, won’t this be recomposed with a non-null user? That means that the null-user branch is dead for the rest of the lifetime of the composable.
c
Hm. I have no idea. lol. @Alex Vanyo do you have any idea?
319 Views