Always Learner
Always Learner

Reputation: 3016

How to navigate from a screen to another in Jetpack Compose using navController?

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?

Upvotes: 3

Views: 6067

Answers (2)

Devrath
Devrath

Reputation: 42854

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

Upvotes: 1

Arpit Shukla
Arpit Shukla

Reputation: 10523

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)

Upvotes: 4

Related Questions