Skip to main content

Command Palette

Search for a command to run...

Passing Arguments While Navigating Back in Jetpack Compose

Updated
6 min read
Passing Arguments While Navigating Back in Jetpack Compose
J

I'm Joel Kanyi, a dedicated professional with a passion for Android and Kotlin Multiplatform development. With a strong background in mobile development, I thrive on creating innovative and user-centric solutions that elevate the Android experience.

The Compose Navigation library assists us in adding navigation capabilities to our apps, making it easier to navigate with arguments to various destinations. But here's the catch: does it support navigating back with arguments when you pop the back stack? Well, the answer is no.

In this post, I want to show you how you can navigate with arguments when you pop items off the back stack in Jetpack Compose navigation.

Let's dive into how we can implement this:

To begin with, let's set up a navigation graph. Make sure you have included the Compose Navigation dependency.

@Composable
fun NavigationGraph(
    navController: NavHostController,
) {
    NavHost(navController = navController, startDestination = "home") {
        composable(
            route = "home",
            arguments = listOf(
                navArgument(name = "takenWater") {
                    type = NavType.BoolType
                    defaultValue = false
                },
            ),

        ) { backStackEntry ->
            val takenWater = backStackEntry.savedStateHandle.getStateFlow(
                "takenWater",
                initialValue = false,
            ).collectAsState().value

            HomeScreen(
                navController = navController,
                takenWater = takenWater,
            )
        }

        composable(route = "take_water") {
            TakeWaterScreen(
                navController = navController,
            )
        }
    }
}

Note that we are monitoring the state of the takenWater using Flows and then passing it to our `HomeScreen`.

val takenWater = backStackEntry.savedStateHandle.getStateFlow(
    "takenWater",
    initialValue = false,
).collectAsState().value

After configuring the navigation, I have developed a HomeScreen that will listen for the state of takenWater This state will be determined by the user's selection on the TakeWaterScreen when the user returns by popping the back stack.

@Composable
fun HomeScreen(
    navController: NavController,
    takenWater: Boolean,
) {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center,
    ) {
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            Text(text = "Taken Water : ${if (takenWater) "Yes" else "No"}")
            Button(onClick = {
                navController.navigate("take_water")
            }) {
                Text(text = "Take Water")
            }
        }
    }
}

Create the TakeWaterScreen, which will be responsible for toggling the variable we want to navigate back with.

Here is the simple code that will be responsible for passing the argument back:

navController.previousBackStackEntry?.savedStateHandle?.set(
    "takenWater",
    takenWater,
)

navController.popBackStack()

If you wish to review the entire TakeWaterScreen, here it is:

@Composable
fun TakeWaterScreen(
    navController: NavController,
) {
    var takenWater by remember {
        mutableStateOf(true)
    }

    Scaffold(
        topBar = {
            TopAppBar(
                navigationIcon = {
                    IconButton(onClick = {
                        navController.previousBackStackEntry?.savedStateHandle?.set(
                            "takenWater",
                            takenWater,
                        )
                        navController.popBackStack()
                    }) {
                        Icon(
                            imageVector = Icons.Default.KeyboardArrowLeft,
                            contentDescription = null,
                        )
                    }
                },

                title = {
                    Text(text = "Take Water")
                },
            )
        },
    ) {
        BackHandler {
            navController.previousBackStackEntry?.savedStateHandle?.set(
                "takenWater",
                takenWater,
            )
            navController.popBackStack()
        }

        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            val radioOptions = listOf("Yes", "No")
            val (selectedOption, onOptionSelected) = remember { mutableStateOf(radioOptions[0]) }
            Column(
                verticalArrangement = Arrangement.spacedBy(12.dp),
            ) {
                radioOptions.forEach { text ->
                    Row(
                        Modifier.padding(horizontal = 4.dp),
                        verticalAlignment = CenterVertically,
                        horizontalArrangement = Arrangement.spacedBy(8.dp),
                    ) {
                        RadioButton(
                            selected = (text == selectedOption),
                            modifier = Modifier
                                .size(24.dp),
                            onClick = {
                                onOptionSelected(text)
                                takenWater = text == "Yes"
                            },
                        )
                        Text(text = text)
                    }
                }
            }
        }
    }
}

That's it! With this simple hack, you can easily pass different arguments when a user navigates back to the previous screen.

You can find all of the results here:

https://github.com/JoelKanyi/NavigateWithArgumentOnPopBackStack