Composable Function

NavDisplay

A nav display that renders and animates between different Scenes, each of which can render one or more NavEntrys.

RevenueCat

RevenueCat

Add subscriptions to your apps in minutes

Ad Get started for free

ConcatenatedBackStackSample

/**
 * In this example, changes to a backStack will only affect the states that were passed alongside
 * that particular backStack into the [rememberDecoratedNavEntries] call.
 */
@Composable
fun ConcatenatedBackStackSample() {
    val tabKeys = listOf(Home, User)
    /** set up User entries */
    val homeBackStack = rememberNavBackStack(Home)
    val homeBackStackNames = homeBackStack.map { (it as SampleKey).name }
    val homeTabEntries =
        getHomeTabEntries(
            backStackString = homeBackStackNames.toString(),
            backStack = homeBackStack,
        )
    /** set up User entries */
    val userBackStack = rememberNavBackStack(User)
    val userTabEntries =
        getUserTabEntries(
            backStackString =
                (homeBackStackNames + userBackStack.map { (it as SampleKey).name }).toString(),
            backStack = userBackStack,
        )
    /** condition for which tab to switch to */
    var currentTab: SampleTabKey by remember { mutableStateOf(Home) }
    Scaffold(
        bottomBar = {
            NavigationBar {
                tabKeys.forEach { tab ->
                    val isSelected = tab == currentTab
                    NavigationBarItem(
                        selected = isSelected,
                        onClick = { currentTab = tab },
                        icon = { Icon(imageVector = tab.imageVector, contentDescription = null) },
                    )
                }
            }
        }
    ) {
        /** Swap backStacks based on the current selected tab */
        NavDisplay(
            entries = if (currentTab == Home) homeTabEntries else homeTabEntries + userTabEntries,
            onBack = {
                val currBackStack = if (currentTab == Home) homeBackStack else userBackStack
                currBackStack.removeLastOrNull()
                if (currentTab == User && userBackStack.isEmpty()) {
                    currentTab = Home
                }
            },
        )
    }
}

MultipleBackStackSample

/**
 * In this example, a backStack's associated decorator states will swap along with the backStack.
 */
@Composable
fun MultipleBackStackSample() {
    val tabKeys = listOf(Home, User)
    /** set up User entries */
    val homeBackStack = rememberNavBackStack(Home)
    val homeTabEntries =
        getHomeTabEntries(
            backStackString = homeBackStack.map { (it as SampleKey).name }.toString(),
            backStack = homeBackStack,
        )
    /** set up User entries */
    val userBackStack = rememberNavBackStack(User)
    val userTabEntries =
        getUserTabEntries(
            backStackString = userBackStack.map { (it as SampleKey).name }.toString(),
            backStack = userBackStack,
        )
    /** condition for which tab to switch to */
    var currentTab: SampleTabKey by remember { mutableStateOf(Home) }
    Scaffold(
        bottomBar = {
            NavigationBar {
                tabKeys.forEach { tab ->
                    val isSelected = tab == currentTab
                    NavigationBarItem(
                        selected = isSelected,
                        onClick = { currentTab = tab },
                        icon = { Icon(imageVector = tab.imageVector, contentDescription = null) },
                    )
                }
            }
        }
    ) {
        /** Swap backStacks based on the current selected tab */
        NavDisplay(
            entries = if (currentTab == Home) homeTabEntries else userTabEntries,
            onBack = {
                val currBackStack = if (currentTab == Home) homeBackStack else userBackStack
                currBackStack.removeLastOrNull()
            },
        )
    }
}

SceneNav

@Composable
fun SceneNav() {
    val backStack = rememberNavBackStack(Profile)
    val showDialog = remember { mutableStateOf(false) }
    NavDisplay(
        backStack = backStack,
        entryDecorators =
            listOf(
                rememberSaveableStateHolderNavEntryDecorator(),
                rememberViewModelStoreNavEntryDecorator(),
            ),
        onBack = { backStack.removeAt(backStack.lastIndex) },
        entryProvider =
            entryProvider({ NavEntry(it) { Text(text = "Invalid Key") } }) {
                entry<Profile> {
                    val viewModel = viewModel<ProfileViewModel>()
                    Profile(viewModel, { backStack.add(it) }) {
                        backStack.removeAt(backStack.lastIndex)
                    }
                }
                entry<Scrollable> {
                    Scrollable({ backStack.add(it) }) { backStack.removeAt(backStack.lastIndex) }
                }
                entry<DialogBase> {
                    DialogBase(onClick = { showDialog.value = true }) {
                        backStack.removeAt(backStack.lastIndex)
                    }
                }
                entry<Dashboard>(
                    metadata =
                        metadata {
                            put(NavDisplay.PredictivePopTransitionKey) { swipeEdge: Int ->
                                if (swipeEdge == NavigationEvent.EDGE_RIGHT) {
                                    slideInHorizontally(tween(700)) { it / 2 } togetherWith
                                        slideOutHorizontally(tween(700)) { -it / 2 }
                                } else {
                                    slideInHorizontally(tween(700)) { -it / 2 } togetherWith
                                        slideOutHorizontally(tween(700)) { it / 2 }
                                }
                            }
                        }
                ) { dashboardArgs ->
                    val userId = dashboardArgs.userId
                    Dashboard(userId, onBack = { backStack.removeAt(backStack.lastIndex) })
                }
            },
    )
    if (showDialog.value) {
        DialogContent(onDismissRequest = { showDialog.value = false })
    }
}

SceneNavSharedElementSample

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SceneNavSharedElementSample() {
    val backStack = rememberNavBackStack(CatList)
    SharedTransitionLayout {
        NavDisplay(
            backStack = backStack,
            onBack = { backStack.removeAt(backStack.lastIndex) },
            entryProvider =
                entryProvider {
                    entry<CatList> {
                        CatList(this@SharedTransitionLayout) { cat ->
                            backStack.add(CatDetail(cat))
                        }
                    }
                    entry<CatDetail> { args ->
                        CatDetail(args.cat, this@SharedTransitionLayout) {
                            backStack.removeAt(backStack.lastIndex)
                        }
                    }
                },
        )
    }
}

SceneNavSharedEntrySample

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SceneNavSharedEntrySample() {
    val backStack = rememberNavBackStack(CatList)
    SharedTransitionLayout {
        NavDisplay(
            backStack = backStack,
            onBack = { backStack.removeAt(backStack.lastIndex) },
            entryDecorators = listOf(rememberSaveableStateHolderNavEntryDecorator()),
            sharedTransitionScope = this,
            entryProvider =
                entryProvider {
                    entry<CatList> {
                        CatList(this@SharedTransitionLayout) { cat ->
                            backStack.add(CatDetail(cat))
                        }
                    }
                    entry<CatDetail> { args ->
                        CatDetail(args.cat, this@SharedTransitionLayout) {
                            backStack.removeAt(backStack.lastIndex)
                        }
                    }
                },
        )
    }
}

SceneStateSample

@Suppress("unused")
@Composable
fun SceneStateSample() {
    val backStack = rememberSaveable { mutableStateListOf("a", "b") }
    val entries =
        rememberDecoratedNavEntries(backStack) { key -> NavEntry(key) { Text("Key = $key") } }
    val sceneState =
        rememberSceneState(
            entries,
            listOf(SinglePaneSceneStrategy()),
            onBack = { backStack.removeLastOrNull() },
        )
    val currentScene = sceneState.currentScene
    val navigationEventState =
        rememberNavigationEventState(
            currentInfo = SceneInfo(currentScene),
            backInfo = sceneState.previousScenes.map { SceneInfo(it) },
        )
    NavigationBackHandler(
        navigationEventState,
        isBackEnabled = currentScene.previousEntries.isNotEmpty(),
        onBackCompleted = {
            // Remove entries from the back stack until we've removed all popped entries
            repeat(entries.size - currentScene.previousEntries.size) {
                backStack.removeLastOrNull()
            }
        },
    )
    NavDisplay(sceneState, navigationEventState)
}