Navigation 3 introduces a powerful and flexible system for managing your app's
UI flow through **Scenes**. Scenes allow you to create highly customized
layouts, adapt to different screen sizes, and manage complex multi-pane
experiences seamlessly.

## Understand Scenes

In Navigation 3, a [`Scene`](https://developer.android.com/reference/kotlin/androidx/navigation3/ui/Scene) is the fundamental unit that renders one or more
`NavEntry` instances. Think of a `Scene` as a distinct visual state or section
of your UI that can contain and manage the display of content from your back
stack.

Each `Scene` instance is uniquely identified by its [`key`](https://developer.android.com/reference/kotlin/androidx/navigation3/ui/Scene#key()) and the class of
the `Scene` itself. This unique identifier is crucial because it drives the
top-level animation when the `Scene` changes.

The `Scene` interface has the following properties:

- `key: Any`: A unique identifier for this specific `Scene` instance. This key, combined with the `Scene`'s class, ensures distinctness, primarily for animation purposes.
- `entries: List<NavEntry<T>>`: This is a list of `NavEntry` objects that the `Scene` is responsible for displaying. Importantly, if the same `NavEntry` is displayed in multiple `Scenes` during a transition (e.g., in a shared element transition), its content will only be rendered by the most recent target `Scene` that is displaying it.
- `previousEntries: List<NavEntry<T>>`: This property defines the `NavEntry`s that would result if a "back" action occurs from the current `Scene`. It's essential for calculating the proper predictive back state, allowing the `NavDisplay` to anticipate and transition to the correct previous state, which may be a Scene with a different class and/or key.
- `content: @Composable () -> Unit`: This is the composable function where you define how the `Scene` renders its `entries` and any surrounding UI elements specific to that `Scene`.

## Understand scene strategies

A [`SceneStrategy`](https://developer.android.com/reference/kotlin/androidx/navigation3/ui/SceneStrategy) is the mechanism that determines how a given list of
`NavEntry`s from the back stack should be arranged and transitioned into a
`Scene`. Essentially, when presented with the current back stack entries, a
`SceneStrategy` asks itself two key questions:

1. **Can I create a `Scene` from these entries?** If the `SceneStrategy` determines it can handle the given `NavEntry`s and form a meaningful `Scene` (e.g., a dialog or a multi-pane layout), it proceeds. Otherwise, it returns `null`, giving other strategies a chance to create a `Scene`.
2. **If so, how should I arrange those entries into the `Scene?`** Once a `SceneStrategy` commits to handling the entries, it takes on the responsibility of constructing a `Scene` and defining how the specified `NavEntry`s will be displayed within that `Scene`.

The core of a `SceneStrategy` is its [`calculateScene`](https://developer.android.com/reference/kotlin/androidx/navigation3/ui/SceneStrategy#calculateScene(kotlin.collections.List,kotlin.Function1)) method:


```kotlin
@Composable
public fun calculateScene(
    entries: List<NavEntry<T>>,
    onBack: (count: Int) -> Unit,
): Scene<T>?  
https://github.com/android/snippets/blob/e79944624dbe60929e307963cc7036898973ab6f/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/scenes/ScenesSnippets.kt#L43-L47
```

<br />

This method takes the current `List<NavEntry<T>>` from the back stack and an
`onBack` callback. It should return a `Scene<T>` if it can successfully form one
from the provided entries, or `null` if it cannot.

`SceneStrategy` also provides a convenient `then` infix function, allowing
you to chain multiple strategies together. This creates a flexible
decision-making pipeline where each strategy can attempt to calculate a `Scene`,
and if it can't, it delegates to the next one in the chain.

## How Scenes and scene strategies work together

The `NavDisplay` is the central composable that observes your back stack and
uses a `SceneStrategy` to determine and render the appropriate `Scene`.

The `NavDisplay's sceneStrategy` parameter expects a `SceneStrategy` that is
responsible for calculating the `Scene` to display. If no `Scene` is calculated
by the provided strategy (or chain of strategies), `NavDisplay` automatically
falls back to using a `SinglePaneSceneStrategy` by default.

Here's a breakdown of the interaction:

- When you add or remove keys from your back stack (e.g., using `backStack.add()` or `backStack.removeLastOrNull()`), the `NavDisplay` observes these changes.
- The `NavDisplay` passes the current list of `NavEntrys` (derived from the back stack keys) to the configured `SceneStrategy's calculateScene` method.
- If the `SceneStrategy` successfully returns a `Scene`, the `NavDisplay` then renders the `content` of that `Scene`. The `NavDisplay` also manages animations and predictive back based on the `Scene`'s properties.

## Example: Single pane layout (default behavior)

The simplest custom layout you can have is a single-pane display, which is the
default behavior if no other `SceneStrategy` takes precedence.


```kotlin
data class SinglePaneScene<T : Any>(
    override val key: Any,
    val entry: NavEntry<T>,
    override val previousEntries: List<NavEntry<T>>,
) : Scene<T> {
    override val entries: List<NavEntry<T>> = listOf(entry)
    override val content: @Composable () -> Unit = { entry.Content() }
}

/**
 * A [SceneStrategy] that always creates a 1-entry [Scene] simply displaying the last entry in the
 * list.
 */
public class SinglePaneSceneStrategy<T : Any> : SceneStrategy<T> {
    @Composable
    override fun calculateScene(entries: List<NavEntry<T>>, onBack: (Int) -> Unit): Scene<T> =
        SinglePaneScene(
            key = entries.last().contentKey,
            entry = entries.last(),
            previousEntries = entries.dropLast(1)
        )
}https://github.com/android/snippets/blob/e79944624dbe60929e307963cc7036898973ab6f/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/scenes/ScenesSnippets.kt#L52-L73
```

<br />

## Example: Basic two-pane layout (custom Scene and strategy)

This example demonstrates how to create a simple two-pane layout that is
activated based on two conditions:

1. The **window width** is sufficiently wide to support two panes (i.e., at least `WIDTH_DP_MEDIUM_LOWER_BOUND`).
2. The **top two entries** on the back stack explicitly declare their support for being displayed in a two-pane layout using specific metadata.

The following snippet is the combined source code for `TwoPaneScene.kt` and
`TwoPaneSceneStrategy.kt`:


```kotlin
// --- TwoPaneScene ---
/**
 * A custom [Scene] that displays two [NavEntry]s side-by-side in a 50/50 split.
 */
class TwoPaneScene<T : Any>(
    override val key: Any,
    override val previousEntries: List<NavEntry<T>>,
    val firstEntry: NavEntry<T>,
    val secondEntry: NavEntry<T>
) : Scene<T> {
    override val entries: List<NavEntry<T>> = listOf(firstEntry, secondEntry)
    override val content: @Composable (() -> Unit) = {
        Row(modifier = Modifier.fillMaxSize()) {
            Column(modifier = Modifier.weight(0.5f)) {
                firstEntry.Content()
            }
            Column(modifier = Modifier.weight(0.5f)) {
                secondEntry.Content()
            }
        }
    }

    companion object {
        internal const val TWO_PANE_KEY = "TwoPane"
        /**
         * Helper function to add metadata to a [NavEntry] indicating it can be displayed
         * in a two-pane layout.
         */
        fun twoPane() = mapOf(TWO_PANE_KEY to true)
    }
}

// --- TwoPaneSceneStrategy ---
/**
 * A [SceneStrategy] that activates a [TwoPaneScene] if the window is wide enough
 * and the top two back stack entries declare support for two-pane display.
 */
class TwoPaneSceneStrategy<T : Any> : SceneStrategy<T> {
    @OptIn(ExperimentalMaterial3AdaptiveApi::class, ExperimentalMaterial3WindowSizeClassApi::class)
    @Composable
    override fun calculateScene(
        entries: List<NavEntry<T>>,
        onBack: (Int) -> Unit
    ): Scene<T>? {

        val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass

        // Condition 1: Only return a Scene if the window is sufficiently wide to render two panes.
        // We use isWidthAtLeastBreakpoint with WIDTH_DP_MEDIUM_LOWER_BOUND (600dp).
        if (!windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND)) {
            return null
        }

        val lastTwoEntries = entries.takeLast(2)

        // Condition 2: Only return a Scene if there are two entries, and both have declared
        // they can be displayed in a two pane scene.
        return if (lastTwoEntries.size == 2 &&
            lastTwoEntries.all { it.metadata.containsKey(TwoPaneScene.TWO_PANE_KEY) }
        ) {
            val firstEntry = lastTwoEntries.first()
            val secondEntry = lastTwoEntries.last()

            // The scene key must uniquely represent the state of the scene.
            val sceneKey = Pair(firstEntry.contentKey, secondEntry.contentKey)

            TwoPaneScene(
                key = sceneKey,
                // Where we go back to is a UX decision. In this case, we only remove the top
                // entry from the back stack, despite displaying two entries in this scene.
                // This is because in this app we only ever add one entry to the
                // back stack at a time. It would therefore be confusing to the user to add one
                // when navigating forward, but remove two when navigating back.
                previousEntries = entries.dropLast(1),
                firstEntry = firstEntry,
                secondEntry = secondEntry
            )
        } else {
            null
        }
    }
}https://github.com/android/snippets/blob/e79944624dbe60929e307963cc7036898973ab6f/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/scenes/ScenesSnippets.kt#L77-L158
```

<br />

To use this `TwoPaneSceneStrategy` in your `NavDisplay`, modify your
`entryProvider` calls to include `TwoPaneScene.twoPane()` metadata for the
entries you intend to show in a two-pane layout. Then, provide
`TwoPaneSceneStrategy()` as your `sceneStrategy`, relying on the default
fallback for single-pane scenarios:


```kotlin
// Define your navigation keys
@Serializable
data object ProductList : NavKey
@Serializable
data class ProductDetail(val id: String) : NavKey

@Composable
fun MyAppContent() {
    val backStack = rememberNavBackStack(ProductList)

    NavDisplay(
        backStack = backStack,
        entryProvider = entryProvider {
            entry<ProductList>(
                // Mark this entry as eligible for two-pane display
                metadata = TwoPaneScene.twoPane()
            ) { key ->
                Column {
                    Text("Product List")
                    Button(onClick = { backStack.add(ProductDetail("ABC")) }) {
                        Text("View Details for ABC (Two-Pane Eligible)")
                    }
                }
            }

            entry<ProductDetail>(
                // Mark this entry as eligible for two-pane display
                metadata = TwoPaneScene.twoPane()
            ) { key ->
                Text("Product Detail: ${key.id} (Two-Pane Eligible)")
            }
            // ... other entries ...
        },
        // Simply provide your custom strategy. NavDisplay will fall back to SinglePaneSceneStrategy automatically.
        sceneStrategy = TwoPaneSceneStrategy<Any>(),
        onBack = { count ->
            repeat(count) {
                if (backStack.isNotEmpty()) {
                    backStack.removeLastOrNull()
                }
            }
        }
    )
}https://github.com/android/snippets/blob/e79944624dbe60929e307963cc7036898973ab6f/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/scenes/ScenesSnippets.kt#L162-L205
```

<br />

## Display list-detail content in a Material Adaptive Scene

For the **list-detail use case** , the
`androidx.compose.material3.adaptive:adaptive-navigation3` artifact provides a
`ListDetailSceneStrategy` that creates a list-detail `Scene`. This `Scene`
automatically handles complex multi-pane arrangements (list, detail, and extra
panes) and adapts them based on window size and device state.

To create a Material list-detail `Scene`, follow these steps:

1. **Add the dependency** : Include `androidx.compose.material3.adaptive:adaptive-navigation3` in your project's `build.gradle.kts` file.
2. **Define your entries with `ListDetailSceneStrategy` metadata** : Use `listPane(), detailPane()`, and `extraPane()` to mark your `NavEntrys` for appropriate pane display. The `listPane()` helper also allows you to specify a `detailPlaceholder` when no item is selected.
3. **Use `rememberListDetailSceneStrategy`** (): This composable function provides a pre-configured `ListDetailSceneStrategy` that can be used by a `NavDisplay`.

The following snippet is a sample `Activity` demonstrating the usage of
`ListDetailSceneStrategy`:


```kotlin
@Serializable
object ProductList : NavKey

@Serializable
data class ProductDetail(val id: String) : NavKey

@Serializable
data object Profile : NavKey

class MaterialListDetailActivity : ComponentActivity() {

    @OptIn(ExperimentalMaterial3AdaptiveApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            Scaffold { paddingValues ->
                val backStack = rememberNavBackStack(ProductList)
                val listDetailStrategy = rememberListDetailSceneStrategy<Any>()

                NavDisplay(
                    backStack = backStack,
                    modifier = Modifier.padding(paddingValues),
                    onBack = { keysToRemove -> repeat(keysToRemove) { backStack.removeLastOrNull() } },
                    sceneStrategy = listDetailStrategy,
                    entryProvider = entryProvider {
                        entry<ProductList>(
                            metadata = ListDetailSceneStrategy.listPane(
                                detailPlaceholder = {
                                    ContentYellow("Choose a product from the list")
                                }
                            )
                        ) {
                            ContentRed("Welcome to Nav3") {
                                Button(onClick = {
                                    backStack.add(ProductDetail("ABC"))
                                }) {
                                    Text("View product")
                                }
                            }
                        }
                        entry<ProductDetail>(
                            metadata = ListDetailSceneStrategy.detailPane()
                        ) { product ->
                            ContentBlue("Product ${product.id} ", Modifier.background(PastelBlue)) {
                                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                                    Button(onClick = {
                                        backStack.add(Profile)
                                    }) {
                                        Text("View profile")
                                    }
                                }
                            }
                        }
                        entry<Profile>(
                            metadata = ListDetailSceneStrategy.extraPane()
                        ) {
                            ContentGreen("Profile")
                        }
                    }
                )
            }
        }
    }
}https://github.com/android/snippets/blob/e79944624dbe60929e307963cc7036898973ab6f/compose/snippets/src/main/java/com/example/compose/snippets/navigation3/scenes/material/MaterialScenesSnippets.kt#L46-L110
```

<br />

**Figure 1**. Example content running in Material list-detail Scene.