Jetpack Compose + Navigation - Infinite loop on navigate() [duplicate]
Asked Answered
Q

1

6

I'm using Jetpack Compose + Navigation (Single Activity, no Fragments) and i'm trying to perform a navigation route as follow:

SplashScreen ---(delay)---> AuthScreen ---(if successful)--> MainScreen

Unfortunately, when i perform Login, the function navigate() in the LoginScreen composables causes an infinite loop. I don't understand if i'm triggering recomposition or what happens. Unfortunately, it's hard to share all the code, but keep in mind that:

  • the issue doesn't seem to be related to LoginScreen and MainScreen composables (you can assume them to be just a simple Text composable)
  • It doesn't seem to be related to NavigationGraph too. Infact, if i just make the SplashScreen --> MainScreen transition there is no problem occurring
  • If i remove the line navController.navigate("main") there is no more loop;
  • The code is based (almost copy-paste) of this example

This is the AuthScreen code where the issue occurs.

@Composable
fun AuthScreen(navController: NavController) {
    val signInRequestCode = 1
    val context = LocalContext.current

    val mSignInViewModel: SignInGoogleViewModel = viewModel(
        factory = SignInGoogleViewModelFactory(context.applicationContext as Application)
    )

    val state = mSignInViewModel.googleUser.observeAsState()
    val user = state.value

    val isError = rememberSaveable { mutableStateOf(false) }


    val authResultLauncher =
        rememberLauncherForActivityResult(contract = GoogleApiContract()) { task ->
            try {
                val gsa = task?.getResult(ApiException::class.java)

                if (gsa != null) {
                    mSignInViewModel.fetchSignInUser(gsa.email, gsa.displayName)
                } else {
                    isError.value = true
                }
            } catch (e: ApiException) {
                Log.e("Authscreen", e.toString())
            }
        }

    AuthView(
        onClick = { authResultLauncher.launch(signInRequestCode) },
        isError = isError.value,
        mSignInViewModel
    )

    Log.d("TEST", "Loop check")  //THIS GOES LIKE CRAZY IN THE LOGCAT!

    user?.let {
        mSignInViewModel.hideLoading()

        //Create JSON adapter to move data
        val moshi = Moshi.Builder().build()
        val jsonAdapter = moshi.adapter(GoogleUserModel::class.java).lenient()
        val userJson = jsonAdapter.toJson(user)

        //Navigate to main screen
        navController.navigate("main")
    }

}

This is the Navigation Graph code:

const val ROOT_ROUTE = "root_route"

@Composable
fun SetupRootNavGraph(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = Screen.SplashScreen.route,
        route = ROOT_ROUTE
    ) {
        composable(Screen.SplashScreen.route) { SplashScreen(navController)}
        composable(Screen.AuthScreen.route) { AuthScreen(navController)}
        composable(Screen.MainScreen.route) {MainScreen(navController)}
    }
}
Quintessence answered 22/2, 2022 at 17:22 Comment(0)
M
15

It's because you're trying to navigate from a composable. See the documentation

You should only call navigate() as part of a callback and not as part of your composable itself, to avoid calling navigate() on every recomposition.

You could use a LaunchEffect for instance

Machicolate answered 22/2, 2022 at 17:35 Comment(3)
Yes, you're right. I coded using bad practices. With your suggestion, the job is done smoothly. By the way, i have a question. I don't get what is triggering recomposition. From what i studied, recomposition is triggered by state modifications. I tested on this fact making the composable stateless (removed "isError" and "state/user" observable), but the infinite loop is still there. Is there anything else i'm not aware that could trigger recomposition? Please consider that "AuthScreen" is the parent composable on the screen.Quintessence
Stateless doesn't mean it won't recompose. If one of its attributes in the parent composable hoisted the state changes, it will recompose. As for your issue, your loop was not about recomposition, but because you were indefinitely navigating to the same screen. You enter composition phase, check user then navigate to the same screen (just different instances).Machicolate
Ok! I think I got your point. Thank you againQuintessence

© 2022 - 2024 — McMap. All rights reserved.