Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 }
    }
  }
}
like image 582
Androider2 Avatar asked Jan 24 '26 15:01

Androider2


2 Answers

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.

like image 151
Philip Dukhov Avatar answered Jan 27 '26 03:01

Philip Dukhov


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" />
like image 29
Jeffrey Liu Avatar answered Jan 27 '26 03:01

Jeffrey Liu



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!