Home > front end >  Invocations can only happen from the context of an @composable function using Compose Navigation
Invocations can only happen from the context of an @composable function using Compose Navigation

Time:10-26

Hi Im currently struggling with navigation in Jetpack Compose due to @composable invocations can only happen from the context of an @composable function. I have a function:

private fun signInResult(result: FirebaseAuthUIAuthenticationResult) {
        val response = result.idpResponse
        if (result.resultCode == RESULT_OK) {
            user = FirebaseAuth.getInstance().currentUser
            Log.e("MainActivity.kt", "Innlogging vellykket")
            ScreenMain()
        } else {
            Log.e("MainActivity.kt", "Feil med innlogging"   response?.error?.errorCode)
        }
    }

and used with my navigation class shown under I only get the error message shown above, how do I fix it?

@Composable
         fun ScreenMain(){
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = Routes.Vareliste.route) {

        composable(Routes.SignUp.route) {
            SignUp(navController = navController)
        }

        composable(Routes.ForgotPassword.route) { navBackStack ->
            ForgotPassword(navController = navController)
        }

        composable(Routes.Vareliste.route) { navBackStack ->
           Vareliste(navController = navController)
       }

       composable(Routes.Handlekurv.route) { navBackStack ->
            Handlekurv(navController = navController)
        }
        
        composable(Routes.Profileromoss.route) { navBackStack ->
            Profileromoss(navController = navController)
        }

   }
}

EDIT WITH COMPLETE CODE Here is the whole code for the class if you guys wanted to see it!

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        JetpackComposeDemoTheme {
             Surface(
                     modifier = Modifier.fillMaxSize(),
                      color = MaterialTheme.colors.background
                            ) {
                LoginPage()
            }
        }
    }
}

private var user: FirebaseUser? = FirebaseAuth.getInstance().currentUser
private lateinit var auth: FirebaseAuth



@Composable
fun LoginPage() {
    Box(modifier = Modifier.fillMaxSize()) {
    }
    Column(
        modifier = Modifier.padding(20.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {

        Text(text = "Velkommen til ITGuys", style = TextStyle(fontSize = 36.sp))

        Spacer(modifier = Modifier.height(20.dp))
        Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
            Button(
                onClick = { signIn() },
                shape = RoundedCornerShape(50.dp),
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
            ) {
                Text(text = "Logg inn")
            }
        }
    }
}

private fun signIn() {
    val providers = arrayListOf(
        AuthUI.IdpConfig.EmailBuilder().build(),
        AuthUI.IdpConfig.GoogleBuilder().build()
    )
    val signinIntent = AuthUI.getInstance()
    .createSignInIntentBuilder()
    .setAvailableProviders(providers)
    .build()

    signInLauncher.launch(signinIntent)
}



private val signInLauncher = registerForActivityResult(
    FirebaseAuthUIActivityResultContract()
) {
    res -> this.signInResult(res)
}

private fun signInResult(result: FirebaseAuthUIAuthenticationResult) {
    val response = result.idpResponse
    if (result.resultCode == RESULT_OK) {
        user = FirebaseAuth.getInstance().currentUser
        Log.e("MainActivity.kt", "Innlogging vellykket")
        ScreenMain()
    } else {
        Log.e("MainActivity.kt", "Feil med innlogging"   response?.error?.errorCode)
    }
}

}

I need to add more text to be allowed to post this much code you can ignore this text cause it is just for being able to post.

CodePudding user response:

As @z.y mentioned, you can pass a lambda with a onFirebaseAuthSuccess. I would also add that as you are passing the navController to the signup screen, the lambda callback you need to pass should look something like

onFirebaseAuthSuccess = { navController.navigate(Routes.Profileromoss.route) } - or whatever route you need

Based on

composable(Routes.SignUp.route) {
            SignUp(navController = navController)
        }

I would assume your signIn screen is called from inside the scope of a composable. If you can add the extract of code containing how you are calling the signInResult function we can be sure about it.

CodePudding user response:

I'm not familiar with Firebase Authentication so I'm not sure where do you call or how you use your signInResult function but you cannot invoke a function that is annotated with @Composable (ScreenMain) from a scope that is not annotated by it such as ordinary function (signInResult).

You can consider adding a lambda callback for signInResult which will be called in the RESULT_OK condition block.

private fun signInResult(result: FirebaseAuthUIAuthenticationResult, onFirebaseAuthSuccess: () -> Unit) {
    val response = result.idpResponse
    if (result.resultCode == RESULT_OK) {
        ...
        ...
        onFirebaseAuthSuccess() // this callback
    } else {
        ...
    }
}

Edit: @sgtpotatoe has better answer, you can invoke a navigation in your root composable from the lambda callback that will navigate to your target screen.

CodePudding user response:

Ok so, in your MainActivity, you want your navigational component to be at the top:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        JetpackComposeDemoTheme {
             Surface(
                     modifier = Modifier.fillMaxSize(),
                      color = MaterialTheme.colors.background
                            ) {
                ScreenMain()
            }
        }
    }
}

Then add a route to your navhost for the login page:

composable(Routes.LoginPage.route) {
            LoginPage(navController = navController)
        }

I think its a bit of a major change, but you would have to rely on a view model to make the authentication, so it can handle the calls, not blocking the ui or showing a loading screen, and communicate with the view

It would look something like this:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    val viewModel = MyViewModel()

    setContent {
        JetpackComposeDemoTheme {
             Surface(
                     modifier = Modifier.fillMaxSize(),
                      color = MaterialTheme.colors.background
                            ) {
                ScreenMain(viewModel)
            }
        }
    }

}

In the LoginPage you want to access the viewmodel to start the auth service calls

In the LoginPage you want to access the viewmodel to observe if the call is succesfull, and in that case do the navigation

In the MyViewModel you want to have the authentication calls, and to update the variable that triggers the navigation if auth is succesfull

Here is an example of a sample firebase authentication app in compose, I would use it as a guide https://firebase.blog/posts/2022/05/adding-firebase-auth-to-jetpack-compose-app

  • Related