0

I have this structure:

val navController = rememberNavController()
NavHost(
    navController = navController,
    startDestination = "auth"
) {
    composable(
        route = "auth"
    ) {
        AuthScreen(
            navController = navController
        )
    }
    composable(
        route = "profile"
    ) {
        ProfileScreen(
            navController = navController
        )
    }
}

When I first time open the app, I display a screen according to the authentication state:

if (!viewModel.isUserAuthenticated) {
    AuthScreen(navController = navController)
} else {
    ProfileScreen(navController = navController)
}

Which works fine. The problem comes, when I try to sing-in in the AuthScreen:

when(val response = authViewModel.signInState.value) {
    is Response.Loading -> CircularProgressIndicator()
    is Response.Success -> {
        if (response.data) {
            navController.navigate("profile")
            Log.d(TAG, "Success")
        }
    }
    is Response.Error -> Log.d(TAG, response.message)
}

The log statement prints "Success" but it doesn't navigate to the next ProfileScreen. How to solve this?

Joan P.
  • 2,368
  • 6
  • 30
  • 63
  • Where have you put the middle if-else condition? You are displaying composables yourself here instead of navigating to them and letting NavController display them. – Arpit Shukla Nov 28 '21 at 09:46
  • @ArpitShukla I put the if statement in `setContent`, right after the first code. Yes, this is what I'm doing, I'm just displaying those screens according to the state. If I try to navigate change `AuthScreen(navController = navController)` with `navController.navigate("auth")`, I get NPE pointing to the `navController.navigate("auth")`, where is said that navController is null. – Joan P. Nov 28 '21 at 09:55
  • Ahh classic. This is the biggest issue of jetpack navigation, completely ignored by a Google. There's no way of changing the "root" screen, so your graph have to have a single point of entry. You'll be better off using simple stack or voyager for that matter – Jakoss Nov 28 '21 at 18:01

2 Answers2

4

You can remove that if-else from the setContent. Instead, make ProfileScreen as the home destination and inside it you can check whether user is authenticated or not. If he is not, navigate to the AuthScreen

@Composable
fun ProfileScreen(navController: NavController) {
    LaunchedEffect(Unit) {
        if(!viewModel.isUserAuthenticated) {
            navController.navigate("auth")
        }
    }
}

If user can logout from this screen (i.e. auth state can change), then instead of Unit use viewModel.isUserAuthenticated as the key for LaunchedEffect (assuming that isUserAuthenticated is a State)

Arpit Shukla
  • 9,612
  • 1
  • 14
  • 40
  • Thanks for the answer. Yes, it's a state. Let me try it. – Joan P. Nov 28 '21 at 11:05
  • It works, but there is an issue. If the user is not authenticated, when I launch the app, it displays the ProfileScreen, and right after that ithe AuthScreen. How to display the AuthScreen first without seeing the ProfileScreen? I upvoted your answer. – Joan P. Nov 28 '21 at 11:13
  • When do you set the value of this boolean? After the `ProfileScreen` is launched? – Arpit Shukla Nov 28 '21 at 11:15
  • In the ViewModel I use `val isUserAuthenticated get() = useCase.isUserAuthenticated()`. Which by default is false, since the user is not authenticated. – Joan P. Nov 28 '21 at 11:16
  • One way could be to wrap everything inside `ProfileScreen` (other than `LaunchedEffect`) in `if(viewModel.isAuthenticated) { ... }` – Arpit Shukla Nov 28 '21 at 11:20
  • So you say to add everything else inside the if statement? But I don't have anything related to the UI. I thin the issue is in the setContent, where I have the code in the first code. Could it be because of that? – Joan P. Nov 28 '21 at 11:24
  • You still have the if-else inside `setContent`? You can remove that. – Arpit Shukla Nov 28 '21 at 11:25
  • No, I don't. That was the first thing I done. I remove that. – Joan P. Nov 28 '21 at 11:26
  • You are seeing the ProfileScreen first as that is the start destination. We navigate to the auth screen only when `ProfileScreen` is loaded, that's why you see a glimpse of `ProfileScreen` on opening the app. – Arpit Shukla Nov 28 '21 at 11:27
  • Btw what did you mean by `But I don't have anything related to the UI.` Don't you have any UI inside `ProfileScreen()` function? – Arpit Shukla Nov 28 '21 at 11:28
  • Ok, I understand why I see the ProfileScreen first, but is there any solution to not see that screen first? As it is confusing the users? Besides that, in the ProfileScreen I only have the log out option in the menu. That's it. Thanks in advance for your patience. – Joan P. Nov 29 '21 at 09:00
  • Did you try putting the entire screen content inside `if(viewModel.isAuthenticated) { ... }` as I suggested above? – Arpit Shukla Nov 29 '21 at 09:18
  • Let me try and get back to you. – Joan P. Nov 29 '21 at 09:18
  • 1
    Glad I could help :) – Arpit Shukla Nov 29 '21 at 09:45
  • Can you please help me with [this](https://stackoverflow.com/questions/70245355/how-to-create-a-viewmodel-object-inside-composable-function-correctly)? Thanks – Joan P. Dec 06 '21 at 12:26
0

Here is a much more detailed answer with code and demo if someone is looking


Code:

ComposeNavigationActivity.kt

class ComposeNavigationActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {Navigation()}
    }
}

Composables

@Composable
fun MainScreen(navController: NavController) {
    var inputFieldText by remember { mutableStateOf("") }
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Red),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        TextField(
            value = inputFieldText,
            onValueChange = {
                inputFieldText = it
            },
            modifier = Modifier.fillMaxWidth().padding(30.dp)
        )
        Spacer(modifier = Modifier.height(20.dp))
        Button(
            modifier = Modifier.padding(5.dp),
            onClick = {
                navController.navigate(Screen.DetailScreen.withArgs(inputFieldText))
            }) {
            Text(
                text = "Navigate",
                color = Color.White,
                textAlign = TextAlign.Center,
                fontSize = 20.sp
            )
        }
    }
}

@Composable
fun DetailScreen(name: String?) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Green),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        name?.let {
            Text(
                text = it,
                color = Color.White,
                textAlign = TextAlign.Center,
                fontSize = 30.sp
            )
        }
    }
}

A Sealed class to track routes

sealed class Screen(val route:String){
    object MainScreen : Screen(route = "main_screen")
    object DetailScreen : Screen(route = "detail_screen")


    fun withArgs(vararg args:String) : String {
        return buildString {
            append(route)
            args.forEach { arg ->
                append("/$arg")
            }
        }
    }
}

Navigation

@Composable
fun Navigation() {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = Screen.MainScreen.route) {
        composable(route = Screen.MainScreen.route) {
            MainScreen(navController = navController)
        }
        composable(
            route = Screen.DetailScreen.route + "/{name}",
            arguments = listOf(
                navArgument("name") {
                    type = NavType.StringType
                    defaultValue = "Some Default"
                    nullable = true
                }
            )
        ) { entry ->
            DetailScreen(name = entry.arguments?.getString("name"))
        }
    }
}

Output

enter image description here

Devrath
  • 42,072
  • 54
  • 195
  • 297