2

My composable is recomposing endlessly after flow collect and navigating to a new screen. I can't understand why.

I'm using Firebase for Auth with Email and Password.

I had to put some Log.i to test my function and my composable, and yes, my Main composable (SignUp) is recomposing endlessly after navigating.

ViewModel

// Firebase auth
private val _signUpState = mutableStateOf<Resources<Any>>(Resources.success(false))
val signUpState: State<Resources<Any>> = _signUpState

fun firebaseSignUp(email: String, password: String) {
    job = viewModelScope.launch(Dispatchers.IO) {
        firebaseAuth.firebaseSignUp(email = email, password = password).collect {
            _signUpState.value = it
            Log.i("balito", "polipop")
        }
    }
}

fun stop() {
    job?.cancel()
}

SignUp

@Composable
fun SignUp(
    navController: NavController,
    signUpViewModel: SignUpViewModel = hiltViewModel()
) {
    val localFocusManager = LocalFocusManager.current
    Log.i("salut", "salut toi")
    Column(
        modifier = Modifier
            .fillMaxSize()
            .systemBarsPadding()
            .padding(16.dp)
            .background(color = PrimaryColor)
    ) {
        BackButton(navController = navController)
        Spacer(modifier = Modifier.height(30.dp))
        Text(
            text = stringResource(id = R.string.sinscrire),
            fontFamily = visby,
            fontWeight = FontWeight.SemiBold,
            fontSize = 28.sp,
            color = Color.White
        )
        Spacer(modifier = Modifier.height(2.dp))
        Text(
            text = stringResource(R.string.prenez_votre_sante_en_main),
            fontFamily = visby,
            fontWeight = FontWeight.SemiBold,
            fontSize = 20.sp,
            color = Grey
        )
        Spacer(modifier = Modifier.height(20.dp))
        Email(signUpViewModel = signUpViewModel, localFocusManager = localFocusManager)
        Spacer(modifier = Modifier.height(16.dp))
        Password(signUpViewModel = signUpViewModel, localFocusManager = localFocusManager)
        Spacer(modifier = Modifier.height(30.dp))
        Button(value = stringResource(R.string.continuer), type = Type.Valid.name) {
            localFocusManager.clearFocus()
            signUpViewModel.firebaseSignUp(signUpViewModel.emailInput.value, signUpViewModel.passwordInput.value)
        }
        Spacer(modifier = Modifier.height(16.dp))
        Button(value = stringResource(R.string.inscription_avec_google), type = Type.Other.name) {

        }
        Spacer(modifier = Modifier.weight(1f))
        Box(
            modifier = Modifier
                .fillMaxWidth(),
            contentAlignment = Alignment.Center
        ) {
            ClickableTextInfo(stringResource(id = R.string.deja_un_compte_se_connecter), onClick = {})
        }
    }
    Response(navController = navController, signUpViewModel = signUpViewModel)
    DisposableEffect(key1 = signUpViewModel.signUpState.value == Resources.success(true)) {
        onDispose {
            signUpViewModel.stop()
            Log.i("fin", "fin")
        }
    }
}

@Composable
private fun Response(
    navController: NavController,
    signUpViewModel: SignUpViewModel
) {
    when (val response = signUpViewModel.signUpState.value) {
        is Resources.Loading<*> -> {
            //WaitingLoaderProgress(loading = true)
        }
        is Resources.Success<*> -> {
            response.data.also {
                Log.i("lolipop", "lolipopi")
                if (it == true) {
                    navController.navigate(Screen.SignUpConfirmation.route)
                }
            }
        }
        is Resources.Failure<*> -> {
//            response.throwable.also {
//                Log.d(TAG, it)
//            }
        }
    }
}
z.g.y
  • 5,512
  • 4
  • 10
  • 36
Mehdi.ncb
  • 346
  • 3
  • 13
  • I'm not very familiar with Compose, but you invoke `firebaseSignUp()` from the composable. Doesn't that mean you start a new signup process with each recomposition? – broot Feb 17 '22 at 14:19
  • 2
    @broot it actually looks like a `onClick` callback, so this part should be fine. But I prefer using named parameters in case of callbacks to make it clear. – Phil Dukhov Feb 17 '22 at 14:23

1 Answers1

4

During navigation transition recomposition happens multiple times because of animations, and you call navController.navigate on each recomposition.

You should not cause side effects or change the state directly from the composable builder, because this will be performed on each recomposition, which is not expected in cases like animation.

Instead you should use side effects. In your case, LaunchedEffect should be used.

if (response.data) {
    LaunchedEffect(Unit) {
        Log.i("lolipop", "lolipopi")
        navController.navigate(Screen.SignUpConfirmation.route)
    }
}
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • it's working but when i'm clicking back button from the device, the screen move from my SecondScreen , to SignUp screen, and instantly to my second screen again. I need to click multiple time quicly to get back to SignUp screen – Mehdi.ncb Feb 17 '22 at 14:32
  • @Mehdi.ncb you can reset your view model state, for example using [DisposableEffect](https://developer.android.com/jetpack/compose/side-effects#disposableeffect) you can wait signup screen to disappear. – Phil Dukhov Feb 17 '22 at 14:36
  • @Mehdi.ncb also check out [this approach](https://stackoverflow.com/a/71036376/3585796) to deliver a single action from your view model to the view – Phil Dukhov Feb 17 '22 at 14:38