diff --git a/README.md b/README.md
index cc5c320..034fc7d 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,8 @@ Examples showing how to use the layouts provided by the [Compose Material3 Adapt
- **[Animations](app/src/main/java/com/example/nav3recipes/animations)**: Shows how to override the default animations for all destinations and a single destination.
### Common use cases
-- **[Common navigation UI](app/src/main/java/com/example/nav3recipes/commonui)**: A common navigation toolbar where each item in the toolbar navigates to a top level destination.
+- **[Common navigation UI](app/src/main/java/com/example/nav3recipes/commonui)**: A common navigation toolbar where each item in the toolbar navigates to a top level destination.
+- **[Multiple back stacks](app/src/main/java/com/example/nav3recipes/multiplestacks)**: Shows how to create multiple top level routes, each with its own back stack. Top level routes are displayed in a navigation bar allowing users to switch between them. State is retained for each top level route, and the navigation state persists config changes and process death.
- **[Conditional navigation](app/src/main/java/com/example/nav3recipes/conditional)**: Switch to a different navigation flow when a condition is met. For example, for authentication or first-time user onboarding.
### Architecture
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8f45afb..a0dfbb7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -149,6 +149,10 @@
android:name=".scenes.listdetail.ListDetailActivity"
android:exported="true"
android:theme="@style/Theme.Nav3Recipes"/>
+
diff --git a/app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt b/app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt
index 81dc297..644a2bb 100644
--- a/app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt
+++ b/app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt
@@ -52,6 +52,7 @@ import com.example.nav3recipes.dialog.DialogActivity
import com.example.nav3recipes.material.listdetail.MaterialListDetailActivity
import com.example.nav3recipes.material.supportingpane.MaterialSupportingPaneActivity
import com.example.nav3recipes.modular.hilt.ModularActivity
+import com.example.nav3recipes.multiplestacks.MultipleStacksActivity
import com.example.nav3recipes.passingarguments.viewmodels.basic.BasicViewModelsActivity
import com.example.nav3recipes.passingarguments.viewmodels.hilt.HiltViewModelsActivity
import com.example.nav3recipes.passingarguments.viewmodels.koin.KoinViewModelsActivity
@@ -92,6 +93,7 @@ private val recipes = listOf(
Heading("Common use cases"),
Recipe("Common UI", CommonUiActivity::class.java),
+ Recipe("Multiple Stacks", MultipleStacksActivity::class.java),
Recipe("Conditional navigation", ConditionalActivity::class.java),
Heading("Architecture"),
diff --git a/app/src/main/java/com/example/nav3recipes/multiplestacks/Content.kt b/app/src/main/java/com/example/nav3recipes/multiplestacks/Content.kt
new file mode 100644
index 0000000..be49f4c
--- /dev/null
+++ b/app/src/main/java/com/example/nav3recipes/multiplestacks/Content.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.nav3recipes.multiplestacks
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.navigation3.runtime.EntryProviderScope
+import androidx.navigation3.runtime.NavKey
+import com.example.nav3recipes.content.ContentGreen
+import com.example.nav3recipes.content.ContentMauve
+import com.example.nav3recipes.content.ContentOrange
+import com.example.nav3recipes.content.ContentPink
+import com.example.nav3recipes.content.ContentPurple
+import com.example.nav3recipes.content.ContentRed
+
+fun EntryProviderScope.featureASection(
+ onSubRouteClick: () -> Unit,
+) {
+ entry {
+ ContentRed("Route A") {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Button(onClick = onSubRouteClick) {
+ Text("Go to A1")
+ }
+ }
+ }
+ }
+ entry {
+ ContentPink("Route A1") {
+ var count by rememberSaveable {
+ mutableIntStateOf(0)
+ }
+
+ Button(onClick = { count++ }) {
+ Text("Value: $count")
+ }
+ }
+ }
+}
+
+fun EntryProviderScope.featureBSection(
+ onSubRouteClick: (id: String) -> Unit,
+) {
+ entry {
+ ContentGreen("Route B") {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Button(onClick = { onSubRouteClick("ABC") }) {
+ Text("Go to B1")
+ }
+ }
+ }
+ }
+ entry {
+ ContentPurple("Route B1") {
+ var count by rememberSaveable {
+ mutableIntStateOf(0)
+ }
+ Button(onClick = { count++ }) {
+ Text("Value: $count")
+ }
+ }
+ }
+}
+
+fun EntryProviderScope.featureCSection(
+ onSubRouteClick: () -> Unit,
+) {
+ entry {
+ ContentMauve("Route C") {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Button(onClick = onSubRouteClick) {
+ Text("Open sub route")
+ }
+ }
+ }
+ }
+ entry {
+ ContentOrange("Route C1") {
+ var count by rememberSaveable {
+ mutableIntStateOf(0)
+ }
+
+ Button(onClick = { count++ }) {
+ Text("Value: $count")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/nav3recipes/multiplestacks/MultipleStacksActivity.kt b/app/src/main/java/com/example/nav3recipes/multiplestacks/MultipleStacksActivity.kt
new file mode 100644
index 0000000..80dea9c
--- /dev/null
+++ b/app/src/main/java/com/example/nav3recipes/multiplestacks/MultipleStacksActivity.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.nav3recipes.multiplestacks
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Camera
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.navigation3.runtime.NavKey
+import androidx.navigation3.runtime.entryProvider
+import androidx.navigation3.ui.NavDisplay
+import com.example.nav3recipes.ui.setEdgeToEdgeConfig
+import kotlinx.serialization.Serializable
+
+
+@Serializable
+data object RouteA : NavKey
+
+@Serializable
+data object RouteA1 : NavKey
+
+@Serializable
+data object RouteB : NavKey
+
+@Serializable
+data object RouteB1 : NavKey
+
+@Serializable
+data object RouteC : NavKey
+
+@Serializable
+data object RouteC1 : NavKey
+
+private val TOP_LEVEL_ROUTES = mapOf(
+ RouteA to NavBarItem(icon = Icons.Default.Home, description = "Route A"),
+ RouteB to NavBarItem(icon = Icons.Default.Face, description = "Route B"),
+ RouteC to NavBarItem(icon = Icons.Default.Camera, description = "Route C"),
+)
+
+data class NavBarItem(
+ val icon: ImageVector,
+ val description: String
+)
+
+class MultipleStacksActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setEdgeToEdgeConfig()
+ super.onCreate(savedInstanceState)
+ setContent {
+ val navigationState = rememberNavigationState(
+ startRoute = RouteA,
+ topLevelRoutes = TOP_LEVEL_ROUTES.keys
+ )
+
+ val navigator = remember { Navigator(navigationState) }
+
+ val entryProvider = entryProvider {
+ featureASection(onSubRouteClick = { navigator.navigate(RouteA1) })
+ featureBSection(onSubRouteClick = { id -> navigator.navigate(RouteB1) })
+ featureCSection(onSubRouteClick = { navigator.navigate(RouteC1) })
+ }
+
+ Scaffold(bottomBar = {
+ NavigationBar {
+ TOP_LEVEL_ROUTES.forEach { (key, value) ->
+ val isSelected = key == navigationState.topLevelRoute
+ NavigationBarItem(
+ selected = isSelected,
+ onClick = { navigator.navigate(key) },
+ icon = {
+ Icon(
+ imageVector = value.icon,
+ contentDescription = value.description
+ )
+ },
+ label = { Text(value.description) }
+ )
+ }
+ }
+ }) { paddingValues ->
+ NavDisplay(
+ entries = navigationState.toEntries(entryProvider),
+ onBack = { navigator.goBack() },
+ modifier = Modifier.padding(paddingValues)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/nav3recipes/multiplestacks/NavigationState.kt b/app/src/main/java/com/example/nav3recipes/multiplestacks/NavigationState.kt
new file mode 100644
index 0000000..c8b8d6b
--- /dev/null
+++ b/app/src/main/java/com/example/nav3recipes/multiplestacks/NavigationState.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.nav3recipes.multiplestacks
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.runtime.toMutableStateList
+import androidx.navigation3.runtime.NavBackStack
+import androidx.navigation3.runtime.NavEntry
+import androidx.navigation3.runtime.NavKey
+import androidx.navigation3.runtime.rememberDecoratedNavEntries
+import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
+
+/**
+ * State holder for navigation state.
+ *
+ * @param topLevelRoute - the current top level route
+ * @param backStacks - the back stacks for each top level route
+ */
+class NavigationState(
+ topLevelRoute: MutableState,
+ val backStacks: Map>
+) {
+ val startRoute = topLevelRoute.value
+ var topLevelRoute : NavKey by topLevelRoute
+ val stacksInUse : List
+ get(){
+ val stacksInUse = mutableListOf(startRoute)
+ if (this@NavigationState.topLevelRoute != startRoute) stacksInUse += this@NavigationState.topLevelRoute
+ return stacksInUse
+ }
+}
+
+/**
+ * Convert NavigationState into NavEntries.
+ */
+@Composable
+fun NavigationState.toEntries(
+ entryProvider: (NavKey) -> NavEntry
+): SnapshotStateList> {
+
+ val decoratedEntries = backStacks.mapValues { (_, stack) ->
+ val decorators = listOf(
+ rememberSaveableStateHolderNavEntryDecorator(),
+ )
+ rememberDecoratedNavEntries(
+ backStack = stack,
+ entryDecorators = decorators,
+ entryProvider = entryProvider
+ )
+ }
+
+ return stacksInUse
+ .flatMap { decoratedEntries[it] ?: emptyList() }
+ .toMutableStateList()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/nav3recipes/multiplestacks/Navigator.kt b/app/src/main/java/com/example/nav3recipes/multiplestacks/Navigator.kt
new file mode 100644
index 0000000..51ffe67
--- /dev/null
+++ b/app/src/main/java/com/example/nav3recipes/multiplestacks/Navigator.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.nav3recipes.multiplestacks
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSerializable
+import androidx.navigation3.runtime.NavKey
+import androidx.navigation3.runtime.rememberNavBackStack
+import androidx.navigation3.runtime.serialization.NavKeySerializer
+import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
+
+/**
+ * Create a navigation state that persists config changes and process death.
+ */
+@Composable
+fun rememberNavigationState(
+ startRoute: NavKey,
+ topLevelRoutes: Set
+) : NavigationState {
+
+ val topLevelRoute = rememberSerializable(
+ serializer = MutableStateSerializer(NavKeySerializer())
+ ){
+ mutableStateOf(startRoute)
+ }
+
+ return NavigationState(
+ topLevelRoute = topLevelRoute,
+ backStacks = topLevelRoutes.associateWith { key ->
+ rememberNavBackStack(key)
+ }
+ )
+}
+
+/**
+ * Handles navigation events (forward and back) by updating the navigation state.
+ */
+class Navigator(val state: NavigationState){
+ fun navigate(route: NavKey){
+ if (route in state.backStacks.keys){
+ // This is a top level route, just switch to it
+ state.topLevelRoute = route
+ } else {
+ state.backStacks[state.topLevelRoute]?.add(route)
+ }
+ }
+
+ fun goBack(){
+
+ val currentStack = state.backStacks[state.topLevelRoute] ?:
+ error("Stack for $state.topLevelRoute not found")
+ val currentRoute = currentStack.last()
+
+ // If we're at the base of the current route, go back to the start route stack.
+ if (currentRoute == state.topLevelRoute){
+ state.topLevelRoute = state.startRoute
+ } else {
+ currentStack.removeLast()
+ }
+ }
+}
+
+
diff --git a/app/src/main/java/com/example/nav3recipes/multiplestacks/README.md b/app/src/main/java/com/example/nav3recipes/multiplestacks/README.md
new file mode 100644
index 0000000..68f67a2
--- /dev/null
+++ b/app/src/main/java/com/example/nav3recipes/multiplestacks/README.md
@@ -0,0 +1,20 @@
+# Multiple back stacks recipe #
+
+This recipe demonstrates how to create multiple back stacks.
+
+The app has three top level routes: `RouteA`, `RouteB` and `RouteC`. These routes have sub routes `RouteA1`, `RouteB1` and `RouteC1` respectively. The content for the sub routes is a counter that can be used to verify state retention through configuration changes and process death.
+
+The app's navigation state is held in the `NavigationState` class. The state itself is created using `rememberNavigationState`.
+
+Navigation events are handled by the `Navigator`. It updates the navigation state.
+
+The navigation state is converted into `NavEntry`s with `NavigationState.toEntries`. These entries are then displayed by `NavDisplay`.
+
+Key behaviors:
+
+- This app follows the "exit through home" pattern where the user always exits through the starting back stack. This means that `RouteA`'s entries are _always_ in the list of entries.
+- Navigating to a top level route that is not the starting route _replaces_ the other entries. For example, navigating A->B->C would result in entries for A+C, B's entries are removed.
+
+Important implementation details:
+
+- Each top level route has its own `SaveableStateHolderNavEntryDecorator`. This is the object responsible for managing the state for the entries in its back stack.