Why the view keeps flashing when using jetpack navigation with Compose?
Asked Answered
C

5

14

I have a login scren and when the login is successful and the view model updates the mutable state variable, my expectation is that a new composable function is called to show a new screen and the login one is removed. The problem is that when the new screen (aka Screen.AccountsScreen) is shown, its content keeps flashing/redrawing and same thing happen with the login form which never gets destroyed (I know this because the log message 'Recomponing...' gets printed endless). I assume this happens because the isLoginSuccessful state is always true. It seems I need an event that can be consumed only once, is this correct? If so, how can I do that?

LoginViewModel.kt

@HiltViewModel
class LoginViewModel @Inject constructor() : ViewModel() {

  var isLoginSuccessful by mutableStateOf(false)
  var errorMessage by mutableStateOf("")
  
  fun onLoginClick(email: String, password:String) {
    errorMessage = ""
    if (credentialsValid(email, password)) {
      isLoginSuccessful = true
    } else {
      errorMessage = "Email or password invalid"
      isLoginSuccessful = false
    }
  }
}

LoginScreen.kt

@Composable
fun loginScreen(
  navController: NavController,
  viewModel: LoginViewModel = hiltViewModel()
) {
  println("Recomponing...")
  // Here gos the code for the login form
  
  if (viewModel.isLoginSuccessful) {
    navController.navigate(Screen.AccountsScreen.route) {
      popUpTo(Screen.LoginScreen.route) { inclusive = true }
    }
  }
}
Cammycamomile answered 7/10, 2021 at 19:55 Comment(4)
Hi! Did my answer solve your question? If so, please accept it using a checkmark under the votes counter. Otherwise, let me know if you have any problems with it.Evita
@Pylyp Dukhov My screens also flash sometimes and I have not yet found out the reason. in my case the action to navigate happens inside a lambda on button click. Afaik I dont need to use a Launched effect in this case as the lambda is not part of the composition but when it happens the state of my composition is lost and I get a screen flash. not sure what I am missingNewberry
@Newberry it's hard to say, please reduce your code to a minimal reproducible example and post an other question, as it doesn't seems to be related to this one.Evita
The thing Is I am not able to reproduce it consistently. I will try to create the conditions to make it happen consistently and post the question thenNewberry
E
20

Composite navigation recomposes both disappearing and appearing views during transition. This is the expected behavior.

You're calling navigate on each recomposition. Your problem lays in these lines:

if (viewModel.isLoginSuccessful) {
    navController.navigate(Screen.AccountsScreen.route) {
        popUpTo(Screen.LoginScreen.route) { inclusive = true }
    }
}

You shouldn't change state directly from view builders. In this case LaunchedEffect should be used:

if (viewModel.isLoginSuccessful) {
    LaunchedEffect(Unit) {
        navController.navigate(Screen.AccountsScreen.route) {
            popUpTo(Screen.LoginScreen.route) { inclusive = true }
        }
    }
}

Check out more in side effects documentation.

Evita answered 8/10, 2021 at 6:57 Comment(2)
You can't launch a composable function inside non composable function.Ilanailangilang
you can use LaunchedEffect(viewModel.isLoginSuccessful) { } as the key to execute the navController callSudorific
G
1

For me, I see flicker because the activity background is white, but I am on dark mode.

Change your app theme to daynight, try adding

implementation 'com.google.android.material:material:1.5.0'

and change your theme to

<style name="Theme.MyStockApp" parent="Theme.Material3.DayNight.NoActionBar" />
Graphemics answered 6/10, 2022 at 19:25 Comment(2)
indeed this is the problem in my case! But adding this theme didn't fix the issue. What I mean is that I see this flashing thing when in dark mode but not when it's notPeppel
I suspect you just hid the flashing, I'd bet you're still getting unwanted recompositions.Fantastically
M
0

For anyone having this issue, a "non hacky" solution has been discussed here: https://mcmap.net/q/829592/-screens-are-flashing-when-i-navigate-to-them-in-dark-theme-using-navigation-compose

Macready answered 7/10, 2023 at 1:24 Comment(0)
E
0

I Got this same problem i got a similiar solution on [this site][1]

val lazyitems = remember {
pager(pagingconfig(/* ... */)) { /* ... */ }
    .flow
    .cachedin(viewmodelscope)
    .collectaslazypagingitems()}


class mainscreenviewmodel : viewmodel() {
val pagingflow = pager(pagingconfig(/* ... */)) { /* ... */ }
    .flow
    .cachedin(viewmodelscope)}
Emotion answered 24/11, 2023 at 7:3 Comment(1)
@composable fun mainscreen( viewmodel = viewmodel<mainscreenviewmodel>() ) { val lazyitems = viewmodel.pagingflow.collectaslazypagingitems() } this is the rest of the codeEmotion
F
0

For me, it was doing a collectAsLazyPagingItems at level that caused a state change on more than just the LazyVerticalColumn that needed it. Causing the entire view to be drawn. Once I moved the collect to a point where only list saw the state change, my flashing went away.

BTW, I discovered this by capturing video of the app while the flash was happening, and I noticed that not only did the list flash, but some other controls on the view were flashing as well. Playing the video at 1/2 speed. I've found that to be very helpful technique for finding rendering glitches.

Fantastically answered 14/4 at 17:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.