Compose Multiplatform Navigation: handle Back button with Navigator BackStack

Compose Multiplatform Navigation: handle Back button with Navigator BackStack

  

I am working on an application, which has a complex nav graph.

The problem is How to handle back button in topAppBar in this situation?

I want to use navController to know it's navigated from MainNav or from other features? to know show back button in topAppBar or not.

//RootNav(no-screen) -> {SplashNav(), MainNav()}

//SplashNav(multiple-screens) -> {MainNav()}

MainNav(no-screen, just BottomBar and NavHost) -> {Feature1Nav(), Feature2Nav(), Feature3Nav()}

Feature1Nav(multiple-screens) -> {Feature2Nav(), Feature3Nav()}

Feature2Nav(multiple-screens) -> {Feature1Nav(), Feature3Nav()}

//Feature3Nav(multiple-screens) -> {Feature1Nav(), Feature2Nav()}

For example if I navigated from

Feature1ListScreen -> Feature2ListScreen -> Feature2DetailScreen

then I should show back button in 2nd and 3rd screen but if I navigated from

Feature2ListScreen -> Feature2DetailScreen

back button only visible in details screen


also there is some of my codes:

feature1:

@Composable
fun Feature1Nav(
    paramFromFeature2: Long? = null,
    detailId: Long?,
    navigateToFeature2: (paramFromFeature1: Long) -> Unit,
) {
    val navigator = rememberNavController()
    NavHost(startDestination =
        if (detailId != null)
            Feature1NavRoots.Feature1Detail.route.plus("/${detailId}")
        else
            Feature1NavRoots.Feature1Listl.route),
        navController = navigator,
        modifier = Modifier.fillMaxSize()
    ) {

        composable(route = Feature1NavRoots.Projects.route) { backStackEntry ->    
            Feature1ListScreen(
                popUp = {
                    navigator.popBackStack()
                },
                navigateToDetail = { id: Long ->
                    navigator.currentBackStackEntry?.arguments?.putLong("id", id)
                    navigator.navigate(
                        Feature1NavRoots.Feature1Detail.route.plus("/$id")
                    )
                },
                navigateToFeature2 = {
                    navigator.currentBackStackEntry?.arguments?.putLong("paramFromFeature1", it)
                    navigator.navigate(
                        Feature1NavRoots.Feature2Nav.route.plus("?paramFromFeature1={it}")
                    )
                },
            )
        }

        composable(route = Feature1NavRoots.ProjectDetail.route.plus("/{id}")) { backStackEntry ->
            val argument = backStackEntry.arguments
            val argId: Long? = argument?.getArgumentLong("id")

            argId?.let {
                Feature1DetailScreen(
                    projectId = argId,
                    popUp = {
                        navigator.popBackStack()
                    },
                )
            }
        }
        composable(route = Feature1NavRoots.Feature2Nav.route.plus("?paramFromFeature1 ={paramFromFeature1}")) { backStackEntry ->
            val argument = backStackEntry.arguments
            val argId: Long? = argument?.getArgumentLong("paramFromFeature1")

            Feature2Nav(
                paramFromFeature1 = argId,
                navigateToFeature1 = {
                    navigator.popBackStack()
                },
                navigator = navigator,
                popUp = {
                    navigator.popBackStack()
                }
            )
        }
    }
}

feature2:

@Composable
fun Feature2Nav(
    paramFromFeature1: Long? = null,
    navigator: NavHostController = rememberNavController(),
    popUp: () -> Unit,
    detailId: Long?,
    navigateToFeature1: (id: Long) -> Unit,
) {
    NavHost(
        startDestination =
        if (detailId != null)
            Feature2NavigationRoots.Feature2Detail.route.plus("/${detailId}")
        else
            Feature2NavigationRoots.Feature2List.route,
        navController = navigator,
        modifier = Modifier.fillMaxSize()
    ) {

        composable(route = Feature2Roots.Feature2List.route) { backStackEntry ->
            Feature2ListScreen(
                filter = filter,
                popUp = {
                    if (navigator.previousBackStackEntry == null) {
                        popUp() // Pop from MainNav if at the root
                    } else {
                        navigator.popBackStack() // Pop within Feature1Nav
                    }
                },
                navigateToProjects = navigateToProjects,
                navigateToDetail = { id: Long ->
                    navigator.navigate(Feature2Roots.Feature2Detail.route.plus("/$id"))
                }
            )
        }

        composable(route = Feature2Roots.Feature2Detail.route.plus("/{id}")) { backStackEntry ->
            val argument = backStackEntry.arguments
            val id = argument?.getArgumentLong("id")
            id?.let {
                Feature2DetailScreen(
                    customerEnquiryId = id,
                    popUp = {
                        navigator.popBackStack()
                    },
                    navigateToProjects = navigateToProjects,
                )
            }
        }
    }
}

MainNav:

@Composable
fun MainScreen(logout: () -> Unit) {

    Scaffold { innerPadding ->
        val navigator = rememberNavController()
        val navBackStackEntry by navigator.currentBackStackEntryAsState()
       // val currentRoute = navBackStackEntry?.destination?.route
       // val containerColor = MaterialTheme.colorScheme.background
       // val selectedColor = MaterialTheme.colorScheme.primary
       // val unselectedColor = MaterialTheme.colorScheme.onBackground

        PermanentNavigationDrawer(
            modifier = Modifier.fillMaxSize(),
            drawerContent = {
                PermanentDrawerSheet(
                    modifier = Modifier.width(200.dp).fillMaxHeight()
                        .verticalScroll(state = rememberScrollState()),
                    drawerContainerColor = containerColor,
                ) {
                    val items = listOf(
                        MenuItems.Feature1,
                        MenuItems.Feature2,
                    )
                    items.forEach {
                        NavigationDrawerItem(label = { Text(text = it.title) },
                            colors = NavigationDrawerItemDefaults.colors(
                                selectedContainerColor = containerColor,
                                unselectedContainerColor = containerColor,
                                selectedIconColor = selectedColor,
                                unselectedIconColor = unselectedColor,
                                selectedTextColor = selectedColor,
                                unselectedTextColor = unselectedColor,
                                selectedBadgeColor = selectedColor,
                                unselectedBadgeColor = unselectedColor,
                            ),
                            selected = it.route == currentRoute,
                            icon = {
                                Icon(
                                    painterResource(if (it.route == currentRoute) it.selectedIcon else it.unSelectedIcon),
                                    it.title
                                )
                            },
                            onClick = {
                                 if (currentRoute != it.route) {
                                    navigator.navigate(it.route) {
                                        navigator.graph.startDestinationRoute?.let { route ->
                                            popUpTo(route) {
                                                saveState = true
                                            }
                                        }
                                        launchSingleTop = true
                                        restoreState = true
                                    }
                                } else {
                                    navigator.navigate(it.route) {
                                        popUpTo(it.route) {
                                            saveState = true
                                        }
                                    }
                                }
                            })
                    }
                }
            },
            content = {
                Box(modifier = Modifier.padding(innerPadding)) {
                    NavHost(
                        startDestination = MainNavigationRoots.Home.route,
                        navController = navigator,
                        modifier = Modifier.fillMaxSize()
                    ) {
                        composable(route = MainNavigationRoots.Feature1.route) {
                            Feature1Nav(
                                navigateToFeature2 = { paramFromFeature1: Long ->
                                    navigator.currentBackStackEntry?.arguments?.putLong(
                                        "paramFromFeature1",
                                        paramFromFeature1
                                    )
                                    navigator.navigate(
                                        route = MainNavigationRoots.Feature2.route,
                                    ) {
                                        navigator.graph.startDestinationRoute?.let { route ->
                                            popUpTo(route) {
                                                saveState = true
                                                inclusive = true
                                            }
                                        }
//                                            launchSingleTop = true
                                        restoreState = true
                                    }
                                },
                            )
                        }
composable(route = MainNavigationRoots.Feature2.route) {
                            Feature2Nav(
                                filter = FilterParams(),
                                navigateToFeature1 = { paramFromFeature2: Long ->
                                    navigator.currentBackStackEntry?.arguments?.putLong(
                                        "paramFromFeature2",
                                        paramFromFeature2
                                    )
                                    navigator.navigate(
                                        route = MainNavigationRoots.Feature1.route,
                                    )
                                },
                                popUp = {
                                    navigator.popBackStack()
                                }
                            )
                        }
                    }
                }
            }
        )
    }
}

I changed names, if you see: Project is feature1 CustomerEnquiry is feature2

Answer

I am doing it inside topBar.

AdaptiveScaffold(
    modifier = Modifier.fillMaxSize(),
    topBar = {
        if (topBarVisibility.value)
            ThesisTopBar(
                backArrowVisibility = topBarBackArrowVisibility.value,
                navController = navController,
            )
    },
    bottomBar = {
        if (bottomBarVisible.value)
            BottomNavigationBar(navController = navController)
    },
) {
    NavHost(
        navController = navController,
        startDestination = INTRO_SPLASH,
        modifier = Modifier.padding(it),
        enterTransition = { EnterTransition.None},
        exitTransition = { ExitTransition.None }
    ){
        introductionGraph(navController)
        mainGraph(navController)
    }
 }

Just passing navController into topBar and adding IconButton into navigationIcon

AdaptiveTopAppBar(
    title = {
        Text(
            text = "Some Text",
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
        )
    },
    navigationIcon = {
        if (backArrowVisibility) {
            IconButton(onClick = { navController.popBackStack() }) {
                Icon(
                    imageVector = Icons.AutoMirrored.Filled.ArrowBack,
                    contentDescription = null,
                )
            }
        }
    },
)
© 2024 Dagalaxy. All rights reserved.