Reputation: 507
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 }
}
}
}
Upvotes: 15
Views: 8388
Reputation: 652
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.
Upvotes: 0
Reputation: 1
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)}
Upvotes: 0
Reputation: 829
For anyone having this issue, a "non hacky" solution has been discussed here: https://stackoverflow.com/a/77248016/8133711
Upvotes: 0
Reputation: 1129
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" />
Upvotes: 3
Reputation: 87864
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.
Upvotes: 21