From 32f612b86a80bc3e8e067c9a7f83df82ac3b5386 Mon Sep 17 00:00:00 2001 From: "Oleksandr.Karpovich" Date: Fri, 5 Dec 2025 15:17:16 +0100 Subject: [PATCH 1/3] Copy Navigation3 1.1.0-alpha01 from dfc34b29f7e --- .../runtime/samples/NavBackStackSamples.kt | 55 ++++-- .../samples/NavBackStackSerializerSamples.kt | 19 +- .../samples/RememberNavBackStackSamples.kt | 11 +- .../navigation3/runtime/NavBackStack.kt | 3 +- navigation3/navigation3-ui/api/current.txt | 15 +- .../navigation3-ui/api/restricted_current.txt | 15 +- .../navigation3-ui/bcv/native/current.txt | 3 + .../demos/HierarchicalSceneSample.kt | 88 +++------ navigation3/navigation3-ui/lint-baseline.xml | 11 +- .../navigation3/ui/samples/BasicSamples.kt | 73 ++------ .../navigation3/scene/SceneStateTest.kt | 157 +++++++++++++++- .../scene/SceneSetupNavEntryDecorator.kt | 5 +- .../androidx/navigation3/scene/SceneState.kt | 98 +++++++--- .../SharedEntryInSceneNavEntryDecorator.kt | 56 ++++++ .../androidx/navigation3/ui/NavDisplay.kt | 176 +++++++++++++++++- 15 files changed, 585 insertions(+), 200 deletions(-) create mode 100644 navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SharedEntryInSceneNavEntryDecorator.kt diff --git a/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/NavBackStackSamples.kt b/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/NavBackStackSamples.kt index a85a488bc9788..9629f6204bf64 100644 --- a/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/NavBackStackSamples.kt +++ b/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/NavBackStackSamples.kt @@ -21,35 +21,60 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.saveable.rememberSerializable import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey -import androidx.navigation3.runtime.samples.NavBackStackSamples.MultiBackStack +import androidx.navigation3.runtime.samples.NavBackStackSamples.Chat +import androidx.navigation3.runtime.samples.NavBackStackSamples.Home +import androidx.navigation3.runtime.samples.NavBackStackSamples.SealedKey +import androidx.navigation3.runtime.samples.NavBackStackSamples.Spaces +import androidx.savedstate.serialization.SavedStateConfiguration import kotlinx.serialization.Serializable +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass import kotlinx.serialization.serializer object NavBackStackSamples { + // --- Open Polymorphism --- @Serializable open class Home : NavKey @Serializable open class Chat : NavKey @Serializable open class Spaces : NavKey + // --- Closed Polymorphism --- @Serializable - class MultiBackStack( - val homeTabStack: NavBackStack, - val chatTabStack: NavBackStack, - val spacesTabStack: NavBackStack, - ) + sealed class SealedKey : NavKey { + + @Serializable class Inbox : SealedKey() + + @Serializable class Settings : SealedKey() + } +} + +@Composable +@Sampled +fun NavBackStack_OpenPolymorphism() { + // https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism + rememberSerializable( + serializer = serializer(), + configuration = + SavedStateConfiguration { + serializersModule = SerializersModule { + polymorphic(baseClass = NavKey::class) { + subclass(clazz = Home::class) + subclass(clazz = Chat::class) + subclass(clazz = Spaces::class) + } + } + }, + ) { + NavBackStack() + } } @Composable @Sampled -fun NavBackStack_Serializer() { - val multiBackStack = - rememberSerializable(serializer = serializer()) { - MultiBackStack( - homeTabStack = NavBackStack(), - chatTabStack = NavBackStack(), - spacesTabStack = NavBackStack(), - ) - } +fun NavBackStack_ClosedPolymorphism() { + // https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#closed-polymorphism + rememberSerializable(serializer = serializer()) { NavBackStack() } } diff --git a/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/NavBackStackSerializerSamples.kt b/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/NavBackStackSerializerSamples.kt index e94bbd3e8f80c..3d74119a4d2df 100644 --- a/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/NavBackStackSerializerSamples.kt +++ b/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/NavBackStackSerializerSamples.kt @@ -20,9 +20,8 @@ import androidx.annotation.Sampled import androidx.compose.runtime.Composable import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey -import androidx.navigation3.runtime.samples.RememberNavBackStackSamples.Details -import androidx.navigation3.runtime.samples.RememberNavBackStackSamples.Home -import androidx.navigation3.runtime.samples.RememberNavBackStackSamples.Screen +import androidx.navigation3.runtime.samples.NavBackStackSerializerSamples.Details +import androidx.navigation3.runtime.samples.NavBackStackSerializerSamples.Home import androidx.navigation3.runtime.serialization.NavBackStackSerializer import androidx.savedstate.serialization.SavedStateConfiguration import androidx.savedstate.serialization.decodeFromSavedState @@ -32,13 +31,11 @@ import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass -object NavBackStackSerializerSamples { +private object NavBackStackSerializerSamples { - @Serializable open class Screen : NavKey + @Serializable open class Home(val id: String) : NavKey - @Serializable open class Home(val id: String) : Screen() - - @Serializable open class Details(val itemId: Long) : Screen() + @Serializable open class Details(val itemId: Long) : NavKey } @Composable @@ -46,7 +43,7 @@ object NavBackStackSerializerSamples { fun NavBackStackSerializer_withReflection() { // On Android, the no-argument overload uses reflection and requires no configuration. // This will throw a runtime exception on non-Android platforms during serialization. - val serializer = NavBackStackSerializer() + val serializer = NavBackStackSerializer() val backStack = NavBackStack(Home("abc"), Details(42)) val encoded = encodeToSavedState(serializer, backStack) @@ -57,14 +54,14 @@ fun NavBackStackSerializer_withReflection() { @Sampled fun NavBackStackSerializer_withSerializersModule() { val module = SerializersModule { - polymorphic(Screen::class) { + polymorphic(NavKey::class) { subclass(Home.serializer()) subclass(Details.serializer()) } } val configuration = SavedStateConfiguration { serializersModule = module } - val serializer = NavBackStackSerializer() + val serializer = NavBackStackSerializer() // Pass the same configuration (or at least its serializersModule) to encode/decode: val backStack = NavBackStack(Home("abc"), Details(42)) diff --git a/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/RememberNavBackStackSamples.kt b/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/RememberNavBackStackSamples.kt index 863a12c8b6f21..55eaf57cd8f91 100644 --- a/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/RememberNavBackStackSamples.kt +++ b/navigation3/navigation3-runtime/samples/src/main/kotlin/androidx/navigation3/runtime/samples/RememberNavBackStackSamples.kt @@ -22,20 +22,17 @@ import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.samples.RememberNavBackStackSamples.Details import androidx.navigation3.runtime.samples.RememberNavBackStackSamples.Home -import androidx.navigation3.runtime.samples.RememberNavBackStackSamples.Screen import androidx.savedstate.serialization.SavedStateConfiguration import kotlinx.serialization.Serializable import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass -object RememberNavBackStackSamples { +private object RememberNavBackStackSamples { - @Serializable open class Screen : NavKey + @Serializable open class Home(val id: String) : NavKey - @Serializable open class Home(val id: String) : Screen() - - @Serializable open class Details(val itemId: Long) : Screen() + @Serializable open class Details(val itemId: Long) : NavKey } @Composable @@ -51,7 +48,7 @@ fun rememberNavBackStack_withSerializersModule() { val config = SavedStateConfiguration { // Register subtypes for open polymorphism or multiplatform use. serializersModule = SerializersModule { - polymorphic(baseClass = Screen::class) { + polymorphic(baseClass = NavKey::class) { subclass(serializer = Home.serializer()) subclass(serializer = Details.serializer()) } diff --git a/navigation3/navigation3-runtime/src/commonMain/kotlin/androidx/navigation3/runtime/NavBackStack.kt b/navigation3/navigation3-runtime/src/commonMain/kotlin/androidx/navigation3/runtime/NavBackStack.kt index e7fd5a7ddc1ae..76eec461aa885 100644 --- a/navigation3/navigation3-runtime/src/commonMain/kotlin/androidx/navigation3/runtime/NavBackStack.kt +++ b/navigation3/navigation3-runtime/src/commonMain/kotlin/androidx/navigation3/runtime/NavBackStack.kt @@ -41,7 +41,8 @@ import kotlinx.serialization.Serializable * backStack.removeLast() // pops stack * ``` * - * @sample androidx.navigation3.runtime.samples.NavBackStack_Serializer + * @sample androidx.navigation3.runtime.samples.NavBackStack_OpenPolymorphism + * @sample androidx.navigation3.runtime.samples.NavBackStack_ClosedPolymorphism * @constructor Creates a new back stack backed by the provided [SnapshotStateList]. * @see rememberNavBackStack for lifecycle-aware persistence. */ diff --git a/navigation3/navigation3-ui/api/current.txt b/navigation3/navigation3-ui/api/current.txt index dbc9f83b0b2e7..0e078be2319d3 100644 --- a/navigation3/navigation3-ui/api/current.txt +++ b/navigation3/navigation3-ui/api/current.txt @@ -48,8 +48,9 @@ package androidx.navigation3.scene { } public final class SceneStateKt { - method @KotlinOnly @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState rememberSceneState(java.util.List> entries, androidx.navigation3.scene.SceneStrategy sceneStrategy, kotlin.jvm.functions.Function0 onBack); - method @BytecodeOnly @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState rememberSceneState(java.util.List!>, androidx.navigation3.scene.SceneStrategy, kotlin.jvm.functions.Function0, androidx.compose.runtime.Composer?, int); + method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState! rememberSceneState(java.util.List!, androidx.navigation3.scene.SceneStrategy!, kotlin.jvm.functions.Function0!, androidx.compose.runtime.Composer!, int); + method @KotlinOnly @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState rememberSceneState(java.util.List> entries, androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SharedTransitionScope? sharedTransitionScope, kotlin.jvm.functions.Function0 onBack); + method @BytecodeOnly @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState rememberSceneState(java.util.List!>, androidx.navigation3.scene.SceneStrategy, androidx.compose.animation.SharedTransitionScope?, kotlin.jvm.functions.Function0, androidx.compose.runtime.Composer?, int, int); } @androidx.compose.runtime.Immutable public fun interface SceneStrategy { @@ -87,10 +88,12 @@ package androidx.navigation3.ui { public final class NavDisplayKt { method @BytecodeOnly @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableTarget(applier="androidx.compose.ui.UiComposable") public static void NavDisplay(androidx.navigation3.scene.SceneState, androidx.navigationevent.compose.NavigationEventState!>, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, androidx.compose.runtime.Composer?, int, int); method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(androidx.navigation3.scene.SceneState sceneState, androidx.navigationevent.compose.NavigationEventState> navigationEventState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec); - method @BytecodeOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, kotlin.jvm.functions.Function0?, java.util.List!>?, androidx.navigation3.scene.SceneStrategy?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>, androidx.compose.runtime.Composer?, int, int, int); - method @BytecodeOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List!>, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, androidx.navigation3.scene.SceneStrategy?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function0, androidx.compose.runtime.Composer?, int, int); - method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List> entries, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec, kotlin.jvm.functions.Function0 onBack); - method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List backStack, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function0 onBack, optional java.util.List> entryDecorators, optional androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec, kotlin.jvm.functions.Function1> entryProvider); + method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List!, androidx.compose.ui.Modifier!, androidx.compose.ui.Alignment!, androidx.navigation3.scene.SceneStrategy!, androidx.compose.animation.SizeTransform!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function2!, kotlin.jvm.functions.Function0!, androidx.compose.runtime.Composer!, int, int); + method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List!, androidx.compose.ui.Modifier!, androidx.compose.ui.Alignment!, kotlin.jvm.functions.Function0!, java.util.List!, androidx.navigation3.scene.SceneStrategy!, androidx.compose.animation.SizeTransform!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function2!, kotlin.jvm.functions.Function1!, androidx.compose.runtime.Composer!, int, int, int); + method @BytecodeOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, kotlin.jvm.functions.Function0?, java.util.List!>?, androidx.navigation3.scene.SceneStrategy?, androidx.compose.animation.SharedTransitionScope?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>, androidx.compose.runtime.Composer?, int, int, int); + method @BytecodeOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List!>, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, androidx.navigation3.scene.SceneStrategy?, androidx.compose.animation.SharedTransitionScope?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function0, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List> entries, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SharedTransitionScope? sharedTransitionScope, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec, kotlin.jvm.functions.Function0 onBack); + method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List backStack, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function0 onBack, optional java.util.List> entryDecorators, optional androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SharedTransitionScope? sharedTransitionScope, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec, kotlin.jvm.functions.Function1> entryProvider); method public static kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> defaultPopTransitionSpec(); method public static kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> defaultPredictivePopTransitionSpec(); method public static kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> defaultTransitionSpec(); diff --git a/navigation3/navigation3-ui/api/restricted_current.txt b/navigation3/navigation3-ui/api/restricted_current.txt index dbc9f83b0b2e7..0e078be2319d3 100644 --- a/navigation3/navigation3-ui/api/restricted_current.txt +++ b/navigation3/navigation3-ui/api/restricted_current.txt @@ -48,8 +48,9 @@ package androidx.navigation3.scene { } public final class SceneStateKt { - method @KotlinOnly @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState rememberSceneState(java.util.List> entries, androidx.navigation3.scene.SceneStrategy sceneStrategy, kotlin.jvm.functions.Function0 onBack); - method @BytecodeOnly @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState rememberSceneState(java.util.List!>, androidx.navigation3.scene.SceneStrategy, kotlin.jvm.functions.Function0, androidx.compose.runtime.Composer?, int); + method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState! rememberSceneState(java.util.List!, androidx.navigation3.scene.SceneStrategy!, kotlin.jvm.functions.Function0!, androidx.compose.runtime.Composer!, int); + method @KotlinOnly @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState rememberSceneState(java.util.List> entries, androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SharedTransitionScope? sharedTransitionScope, kotlin.jvm.functions.Function0 onBack); + method @BytecodeOnly @androidx.compose.runtime.Composable public static androidx.navigation3.scene.SceneState rememberSceneState(java.util.List!>, androidx.navigation3.scene.SceneStrategy, androidx.compose.animation.SharedTransitionScope?, kotlin.jvm.functions.Function0, androidx.compose.runtime.Composer?, int, int); } @androidx.compose.runtime.Immutable public fun interface SceneStrategy { @@ -87,10 +88,12 @@ package androidx.navigation3.ui { public final class NavDisplayKt { method @BytecodeOnly @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposableTarget(applier="androidx.compose.ui.UiComposable") public static void NavDisplay(androidx.navigation3.scene.SceneState, androidx.navigationevent.compose.NavigationEventState!>, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, androidx.compose.runtime.Composer?, int, int); method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(androidx.navigation3.scene.SceneState sceneState, androidx.navigationevent.compose.NavigationEventState> navigationEventState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec); - method @BytecodeOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, kotlin.jvm.functions.Function0?, java.util.List!>?, androidx.navigation3.scene.SceneStrategy?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>, androidx.compose.runtime.Composer?, int, int, int); - method @BytecodeOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List!>, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, androidx.navigation3.scene.SceneStrategy?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function0, androidx.compose.runtime.Composer?, int, int); - method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List> entries, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec, kotlin.jvm.functions.Function0 onBack); - method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List backStack, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function0 onBack, optional java.util.List> entryDecorators, optional androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec, kotlin.jvm.functions.Function1> entryProvider); + method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List!, androidx.compose.ui.Modifier!, androidx.compose.ui.Alignment!, androidx.navigation3.scene.SceneStrategy!, androidx.compose.animation.SizeTransform!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function2!, kotlin.jvm.functions.Function0!, androidx.compose.runtime.Composer!, int, int); + method @BytecodeOnly @Deprecated @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List!, androidx.compose.ui.Modifier!, androidx.compose.ui.Alignment!, kotlin.jvm.functions.Function0!, java.util.List!, androidx.navigation3.scene.SceneStrategy!, androidx.compose.animation.SizeTransform!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function1!, kotlin.jvm.functions.Function2!, kotlin.jvm.functions.Function1!, androidx.compose.runtime.Composer!, int, int, int); + method @BytecodeOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, kotlin.jvm.functions.Function0?, java.util.List!>?, androidx.navigation3.scene.SceneStrategy?, androidx.compose.animation.SharedTransitionScope?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>, androidx.compose.runtime.Composer?, int, int, int); + method @BytecodeOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List!>, androidx.compose.ui.Modifier?, androidx.compose.ui.Alignment?, androidx.navigation3.scene.SceneStrategy?, androidx.compose.animation.SharedTransitionScope?, androidx.compose.animation.SizeTransform?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function1!>!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function2!>!,? super java.lang.Integer!,androidx.compose.animation.ContentTransform!>?, kotlin.jvm.functions.Function0, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List> entries, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SharedTransitionScope? sharedTransitionScope, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec, kotlin.jvm.functions.Function0 onBack); + method @KotlinOnly @androidx.compose.runtime.Composable public static void NavDisplay(java.util.List backStack, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function0 onBack, optional java.util.List> entryDecorators, optional androidx.navigation3.scene.SceneStrategy sceneStrategy, optional androidx.compose.animation.SharedTransitionScope? sharedTransitionScope, optional androidx.compose.animation.SizeTransform? sizeTransform, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> transitionSpec, optional kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> popTransitionSpec, optional kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> predictivePopTransitionSpec, kotlin.jvm.functions.Function1> entryProvider); method public static kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> defaultPopTransitionSpec(); method public static kotlin.jvm.functions.Function2>,java.lang.Integer,androidx.compose.animation.ContentTransform> defaultPredictivePopTransitionSpec(); method public static kotlin.jvm.functions.Function1>,androidx.compose.animation.ContentTransform> defaultTransitionSpec(); diff --git a/navigation3/navigation3-ui/bcv/native/current.txt b/navigation3/navigation3-ui/bcv/native/current.txt index 8a372425b479c..5fb77fc0bc4a6 100644 --- a/navigation3/navigation3-ui/bcv/native/current.txt +++ b/navigation3/navigation3-ui/bcv/native/current.txt @@ -93,9 +93,12 @@ final val androidx.navigation3.ui/LocalNavAnimatedContentScope // androidx.navig final fun (): androidx.compose.runtime/ProvidableCompositionLocal // androidx.navigation3.ui/LocalNavAnimatedContentScope.|(){}[0] final val androidx.navigation3.ui/androidx_navigation3_ui_NavDisplay$stableprop // androidx.navigation3.ui/androidx_navigation3_ui_NavDisplay$stableprop|#static{}androidx_navigation3_ui_NavDisplay$stableprop[0] +final fun <#A: kotlin/Any> androidx.navigation3.scene/rememberSceneState(kotlin.collections/List>, androidx.navigation3.scene/SceneStrategy<#A>, androidx.compose.animation/SharedTransitionScope?, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int): androidx.navigation3.scene/SceneState<#A> // androidx.navigation3.scene/rememberSceneState|rememberSceneState(kotlin.collections.List>;androidx.navigation3.scene.SceneStrategy<0:0>;androidx.compose.animation.SharedTransitionScope?;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.scene/rememberSceneState(kotlin.collections/List>, androidx.navigation3.scene/SceneStrategy<#A>, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int): androidx.navigation3.scene/SceneState<#A> // androidx.navigation3.scene/rememberSceneState|rememberSceneState(kotlin.collections.List>;androidx.navigation3.scene.SceneStrategy<0:0>;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(androidx.navigation3.scene/SceneState<#A>, androidx.navigationevent.compose/NavigationEventState>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(androidx.navigation3.scene.SceneState<0:0>;androidx.navigationevent.compose.NavigationEventState>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] +final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(kotlin.collections/List<#A>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, kotlin/Function0?, kotlin.collections/List>?, androidx.navigation3.scene/SceneStrategy<#A>?, androidx.compose.animation/SharedTransitionScope?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, kotlin/Function1<#A, androidx.navigation3.runtime/NavEntry<#A>>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(kotlin.collections.List<0:0>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;kotlin.Function0?;kotlin.collections.List>?;androidx.navigation3.scene.SceneStrategy<0:0>?;androidx.compose.animation.SharedTransitionScope?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;kotlin.Function1<0:0,androidx.navigation3.runtime.NavEntry<0:0>>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(kotlin.collections/List<#A>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, kotlin/Function0?, kotlin.collections/List>?, androidx.navigation3.scene/SceneStrategy<#A>?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, kotlin/Function1<#A, androidx.navigation3.runtime/NavEntry<#A>>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(kotlin.collections.List<0:0>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;kotlin.Function0?;kotlin.collections.List>?;androidx.navigation3.scene.SceneStrategy<0:0>?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;kotlin.Function1<0:0,androidx.navigation3.runtime.NavEntry<0:0>>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int;kotlin.Int){0§}[0] +final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(kotlin.collections/List>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, androidx.navigation3.scene/SceneStrategy<#A>?, androidx.compose.animation/SharedTransitionScope?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(kotlin.collections.List>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;androidx.navigation3.scene.SceneStrategy<0:0>?;androidx.compose.animation.SharedTransitionScope?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(kotlin.collections/List>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, androidx.navigation3.scene/SceneStrategy<#A>?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(kotlin.collections.List>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;androidx.navigation3.scene.SceneStrategy<0:0>?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/defaultPopTransitionSpec(): kotlin/Function1>, androidx.compose.animation/ContentTransform> // androidx.navigation3.ui/defaultPopTransitionSpec|defaultPopTransitionSpec(){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/defaultPredictivePopTransitionSpec(): kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform> // androidx.navigation3.ui/defaultPredictivePopTransitionSpec|defaultPredictivePopTransitionSpec(){0§}[0] diff --git a/navigation3/navigation3-ui/integration-tests/navigation3-demos/src/main/kotlin/androidx/navigation3/demos/HierarchicalSceneSample.kt b/navigation3/navigation3-ui/integration-tests/navigation3-demos/src/main/kotlin/androidx/navigation3/demos/HierarchicalSceneSample.kt index bf0b69adf0e11..2b2fff87fb634 100644 --- a/navigation3/navigation3-ui/integration-tests/navigation3-demos/src/main/kotlin/androidx/navigation3/demos/HierarchicalSceneSample.kt +++ b/navigation3/navigation3-ui/integration-tests/navigation3-demos/src/main/kotlin/androidx/navigation3/demos/HierarchicalSceneSample.kt @@ -18,7 +18,6 @@ package androidx.navigation3.demos import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout -import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -27,9 +26,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableIntStateOf @@ -42,12 +38,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.navigation3.runtime.NavEntry -import androidx.navigation3.runtime.NavEntryDecorator -import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.scene.Scene import androidx.navigation3.scene.SceneStrategy import androidx.navigation3.scene.SceneStrategyScope -import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.navigation3.ui.NavDisplay import androidx.savedstate.compose.serialization.serializers.SnapshotStateListSerializer import kotlin.collections.forEach @@ -63,38 +56,9 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -/** The [SharedTransitionScope] of the [NavDisplay]. */ -@OptIn(ExperimentalSharedTransitionApi::class) -val LocalNavSharedTransitionScope: ProvidableCompositionLocal = - compositionLocalOf { - throw IllegalStateException( - "Unexpected access to LocalNavSharedTransitionScope. You must provide a " + - "SharedTransitionScope from a call to SharedTransitionLayout() or " + - "SharedTransitionScope()" - ) - } - @OptIn(ExperimentalSharedTransitionApi::class, ExperimentalUuidApi::class) @Composable fun HierarchicalSceneSample() { - /** - * A [NavEntryDecorator] that wraps each entry in a shared element that is controlled by the - * [Scene]. - */ - val sharedEntryInSceneNavEntryDecorator = - NavEntryDecorator { entry -> - with(LocalNavSharedTransitionScope.current) { - Box( - Modifier.sharedElement( - rememberSharedContentState(entry.contentKey), - animatedVisibilityScope = LocalNavAnimatedContentScope.current, - ) - ) { - entry.Content() - } - } - } - var columns by rememberSaveable { mutableIntStateOf(1) } val backStack: MutableList = @@ -112,35 +76,29 @@ fun HierarchicalSceneSample() { } } SharedTransitionLayout { - CompositionLocalProvider(LocalNavSharedTransitionScope provides this) { - NavDisplay( - backStack = backStack, - onBack = { backStack.removeAt(backStack.lastIndex) }, - entryDecorators = - listOf( - sharedEntryInSceneNavEntryDecorator, - rememberSaveableStateHolderNavEntryDecorator(), - ), - sceneStrategy = sceneStrategy, - ) { - NavEntry(key = it, contentKey = it.id) { entry -> - Box( - modifier = Modifier.background(entry.color).fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(entry.color.toString()) - - var counter by rememberSaveable { mutableIntStateOf(0) } - - Row(verticalAlignment = Alignment.CenterVertically) { - Button(onClick = { counter-- }) { Text("-") } - Text( - "rememberSaveable counter: $counter", - modifier = Modifier.weight(1f, fill = false), - ) - Button(onClick = { counter++ }) { Text("+") } - } + NavDisplay( + backStack = backStack, + onBack = { backStack.removeAt(backStack.lastIndex) }, + sceneStrategy = sceneStrategy, + sharedTransitionScope = this, + ) { + NavEntry(key = it, contentKey = it.id) { entry -> + Box( + modifier = Modifier.background(entry.color).fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text(entry.color.toString()) + + var counter by rememberSaveable { mutableIntStateOf(0) } + + Row(verticalAlignment = Alignment.CenterVertically) { + Button(onClick = { counter-- }) { Text("-") } + Text( + "rememberSaveable counter: $counter", + modifier = Modifier.weight(1f, fill = false), + ) + Button(onClick = { counter++ }) { Text("+") } } } } diff --git a/navigation3/navigation3-ui/lint-baseline.xml b/navigation3/navigation3-ui/lint-baseline.xml index ae63addec6e67..a9c1e30b281cb 100644 --- a/navigation3/navigation3-ui/lint-baseline.xml +++ b/navigation3/navigation3-ui/lint-baseline.xml @@ -1,5 +1,5 @@ - + + + + + = - compositionLocalOf { - throw IllegalStateException( - "Unexpected access to LocalNavSharedTransitionScope. You must provide a " + - "SharedTransitionScope from a call to SharedTransitionLayout() or " + - "SharedTransitionScope()" - ) - } - - /** - * A [NavEntryDecorator] that wraps each entry in a shared element that is controlled by the - * [Scene]. - */ - val sharedEntryInSceneNavEntryDecorator = - NavEntryDecorator { entry -> - with(localNavSharedTransitionScope.current) { - Box( - Modifier.sharedElement( - rememberSharedContentState(entry.contentKey), - animatedVisibilityScope = LocalNavAnimatedContentScope.current, - ) - ) { - entry.Content() - } - } - } - val backStack = rememberNavBackStack(CatList) SharedTransitionLayout { - CompositionLocalProvider(localNavSharedTransitionScope provides this) { - NavDisplay( - backStack = backStack, - onBack = { backStack.removeAt(backStack.lastIndex) }, - entryDecorators = - listOf( - sharedEntryInSceneNavEntryDecorator, - rememberSaveableStateHolderNavEntryDecorator(), - ), - entryProvider = - entryProvider { - entry { - CatList(this@SharedTransitionLayout) { cat -> - backStack.add(CatDetail(cat)) - } + NavDisplay( + backStack = backStack, + onBack = { backStack.removeAt(backStack.lastIndex) }, + entryDecorators = listOf(rememberSaveableStateHolderNavEntryDecorator()), + sharedTransitionScope = this, + entryProvider = + entryProvider { + entry { + CatList(this@SharedTransitionLayout) { cat -> + backStack.add(CatDetail(cat)) } - entry { args -> - CatDetail(args.cat, this@SharedTransitionLayout) { - backStack.removeAt(backStack.lastIndex) - } + } + entry { args -> + CatDetail(args.cat, this@SharedTransitionLayout) { + backStack.removeAt(backStack.lastIndex) } - }, - ) - } + } + }, + ) } } diff --git a/navigation3/navigation3-ui/src/androidDeviceTest/kotlin/androidx/navigation3/scene/SceneStateTest.kt b/navigation3/navigation3-ui/src/androidDeviceTest/kotlin/androidx/navigation3/scene/SceneStateTest.kt index f5567c6fc9a75..952fc8e0d7adc 100644 --- a/navigation3/navigation3-ui/src/androidDeviceTest/kotlin/androidx/navigation3/scene/SceneStateTest.kt +++ b/navigation3/navigation3-ui/src/androidDeviceTest/kotlin/androidx/navigation3/scene/SceneStateTest.kt @@ -16,9 +16,12 @@ package androidx.navigation3.scene +import androidx.compose.runtime.getValue import androidx.compose.runtime.mock.Text import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.test.junit4.createComposeRule import androidx.kruth.assertThat import androidx.navigation3.runtime.NavEntry @@ -108,10 +111,158 @@ internal class SceneStateTest { rule.waitForIdle() assertThat(currentScene).isInstanceOf>() } + + @Test + fun testSceneStateDoesNotRecalculateOnUnrelatedRecomposition() { + val strategy = CountingSceneStrategy() + val backStack = mutableStateListOf(First) + val sceneStates = mutableSetOf>() + + // Unrelated state we'll use to trigger recomposition. + var tick by mutableStateOf(0) + + rule.setContent { + val entries = + rememberDecoratedNavEntries( + backStack, + entryDecorators = emptyList(), + entryProvider { entry { Text("First") } }, + ) + + // Read tick to participate in recomposition without changing inputs. + @Suppress("UnusedVariable", "unused") val unused = tick + + sceneStates += rememberSceneState(entries, strategy, onBack = {}) + } + + // First composition should call calculate once. + assertThat(strategy.calculateSceneInvocations).isEqualTo(1) + + // Trigger recomposition. + rule.runOnIdle { tick++ } + rule.runOnIdle { tick++ } + rule.runOnIdle { tick++ } + + // After recomposition, still only one calculation. + rule.runOnIdle { assertThat(strategy.calculateSceneInvocations).isEqualTo(1) } + + // Sanity check. + rule.runOnIdle { + assertThat(sceneStates.first().currentScene).isInstanceOf>() + } + rule.runOnIdle { assertThat(sceneStates.size).isEqualTo(1) } + } + + @Test + fun testSceneStateDoesNotRecalculateOnOnBackChange() { + val strategy = CountingSceneStrategy() + + // Unrelated state we'll use to trigger recomposition. + var tick by mutableStateOf(0) + + rule.setContent { + val entries = + rememberDecoratedNavEntries( + listOf(First), + entryDecorators = emptyList(), + entryProvider { entry { Text("First") } }, + ) + + // This creates a new lambda instance on every recomposition + // that captures the current 'tick'. + val unstableOnBack = { + @Suppress("UNUSED_VARIABLE") val unused = tick + } + + rememberSceneState(entries, strategy, onBack = unstableOnBack) + } + + // First composition should call calculate once. + rule.runOnIdle { assertThat(strategy.calculateSceneInvocations).isEqualTo(1) } + + // Trigger recomposition, which creates a new 'unstableOnBack' lambda. + rule.runOnIdle { tick++ } + rule.runOnIdle { tick++ } + + // After recomposition, calculation should NOT have run again. + rule.runOnIdle { assertThat(strategy.calculateSceneInvocations).isEqualTo(1) } + } + + @Test + fun testSceneStateRecalculatesOnEntriesChange() { + val strategy = CountingSceneStrategy() + val backStack = mutableStateListOf(First) + + rule.setContent { + val entries = + rememberDecoratedNavEntries( + backStack, + entryDecorators = emptyList(), + entryProvider { + entry { Text("First") } + entry { Text("Second") } + }, + ) + rememberSceneState(entries, strategy, onBack = {}) + } + + // First composition should call calculate once. + rule.runOnIdle { assertThat(strategy.calculateSceneInvocations).isEqualTo(1) } + + // Trigger recomposition by changing entries. + rule.runOnIdle { backStack += Second } + + // After recomposition with new entries, calculation should run again. + rule.runOnIdle { assertThat(strategy.calculateSceneInvocations).isGreaterThan(1) } + } + + @Test + fun testSceneStateRecalculatesOnStrategyChange() { + val initialStrategy = CountingSceneStrategy() + val newStrategy = CountingSceneStrategy() + var strategy: SceneStrategy by mutableStateOf(initialStrategy) + + rule.setContent { + val entries = + rememberDecoratedNavEntries( + listOf(First), + entryDecorators = emptyList(), + entryProvider { entry { Text("First") } }, + ) + rememberSceneState(entries, strategy, onBack = {}) + } + + // First composition should call calculate once on the initial strategy. + rule.runOnIdle { assertThat(initialStrategy.calculateSceneInvocations).isEqualTo(1) } + rule.runOnIdle { assertThat(newStrategy.calculateSceneInvocations).isEqualTo(0) } + + // Trigger recomposition by changing the strategy instance. + rule.runOnIdle { strategy = newStrategy } + + // The new strategy should now be used, incrementing its count. + rule.runOnIdle { assertThat(initialStrategy.calculateSceneInvocations).isEqualTo(1) } + rule.runOnIdle { assertThat(newStrategy.calculateSceneInvocations).isEqualTo(1) } + } } -object First +private object First + +private object Second + +private object Third + +/** Minimal strategy that counts calls to [calculateSceneWithSinglePaneFallback]. */ +private class CountingSceneStrategy() : SceneStrategy { -object Second + private val base = SinglePaneSceneStrategy() -object Third + var calculateSceneInvocations = 0 + private set + + override fun SceneStrategyScope.calculateScene(entries: List>): Scene? { + calculateSceneInvocations++ + with(base) { + return calculateScene(entries) + } + } +} diff --git a/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SceneSetupNavEntryDecorator.kt b/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SceneSetupNavEntryDecorator.kt index b1a51a684d8e2..1b302d6261e5f 100644 --- a/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SceneSetupNavEntryDecorator.kt +++ b/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SceneSetupNavEntryDecorator.kt @@ -38,8 +38,9 @@ internal fun rememberSceneSetupNavEntryDecorator(): SceneSetupNavEntry * the same entry content is not composed multiple times in different places of the hierarchy by * different scenes. * - * This should likely be the first [NavEntryDecorator] to ensure that other [NavEntryDecorator] - * calls that are stateful are moved properly inside the [movableContentOf]. + * This should likely be the first [NavEntryDecorator] (with the exception of the + * [SharedEntryInSceneNavEntryDecorator]) to ensure that other [NavEntryDecorator] calls that are + * stateful are moved properly inside the [movableContentOf]. */ internal class SceneSetupNavEntryDecorator( val movableContentMap: MutableMap Unit) -> Unit> = diff --git a/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SceneState.kt b/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SceneState.kt index 88bc0af35ce5b..f4d931a0c5bcf 100644 --- a/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SceneState.kt +++ b/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SceneState.kt @@ -16,12 +16,15 @@ package androidx.navigation3.scene +import androidx.compose.animation.SharedTransitionScope import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.util.fastMap import androidx.navigation3.runtime.NavEntry import androidx.navigation3.runtime.rememberDecoratedNavEntries -import kotlin.collections.plusAssign /** * Returns a [SceneState] that is remembered across compositions based on the parameters. @@ -33,30 +36,78 @@ import kotlin.collections.plusAssign * @param onBack a callback for handling system back press. * @sample androidx.navigation3.scene.samples.SceneStateSample */ +@Deprecated( + message = "Deprecated in favor of rememberSceneState that supports sharedTransitionScope", + level = DeprecationLevel.HIDDEN, +) @Composable public fun rememberSceneState( entries: List>, sceneStrategy: SceneStrategy, onBack: () -> Unit, -): SceneState = - with(SceneStrategyScope(onBack)) { - // Re-wrap the entries with: - // - SceneSetupNavEntryDecorator to ensure all the ensures are inside of a moveable content - // - BackStackAwareLifecycleNavEntryDecorator to ensure that the Lifecycle of entries that - // are no longer on the back stack is capped at CREATED - val decoratedEntries = - rememberDecoratedNavEntries( - entries, - listOf( - rememberSceneSetupNavEntryDecorator(), - rememberBackStackAwareLifecycleNavEntryDecorator(entries), - ), +): SceneState { + return rememberSceneState( + entries = entries, + sceneStrategy = sceneStrategy, + sharedTransitionScope = null, + onBack = onBack, + ) +} + +/** + * Returns a [SceneState] that is remembered across compositions based on the parameters. + * + * This calculates all of the scenes and provides them in a [SceneState]. + * + * @param entries all of the entries that are associated with this state + * @param sceneStrategy the [SceneStrategy] to determine which scene to render a list of entries. + * @param sharedTransitionScope the [SharedTransitionScope] needed to wrap the scene decorator. If + * this parameter is added, this function will require the [LocalNavAnimatedContentScope]. + * @param onBack a callback for handling system back press. + * @sample androidx.navigation3.scene.samples.SceneStateSample + */ +@Composable +public fun rememberSceneState( + entries: List>, + sceneStrategy: SceneStrategy, + sharedTransitionScope: SharedTransitionScope? = null, + onBack: () -> Unit, +): SceneState { + val currentOnBack by rememberUpdatedState(onBack) + + val sharedElementDecorator: SharedEntryInSceneNavEntryDecorator? = + sharedTransitionScope?.let { rememberSharedEntryInSceneNavEntryDecorator(it) } + + // Re-wrap the entries with: + // - SharedEntryInSceneNavEntryDecorator to allow entries between scenes to be animated + // - SceneSetupNavEntryDecorator to ensure all the ensures are inside of a moveable content + // - BackStackAwareLifecycleNavEntryDecorator to ensure that the Lifecycle of entries that + // are no longer on the back stack is capped at CREATED + val decoratedEntries = + rememberDecoratedNavEntries( + entries, + listOfNotNull( + sharedElementDecorator, + rememberSceneSetupNavEntryDecorator(), + rememberBackStackAwareLifecycleNavEntryDecorator(entries), + ), + ) + + return remember(sceneStrategy, decoratedEntries) { + val scope = + SceneStrategyScope( + // `currentOnBack` invokes the *latest* `onBack` lambda. The outer + // `remember` block intentionally skips `onBack` as a key to avoid + // recalculating all scenes when just the `onBack` instance changes. + onBack = @Suppress("UnnecessaryLambdaCreation") { currentOnBack() } ) + // Calculate the single scene based on the sceneStrategy and start the list there. val allScenes = mutableListOf( - sceneStrategy.calculateSceneWithSinglePaneFallback(this, decoratedEntries) + sceneStrategy.calculateSceneWithSinglePaneFallback(scope, decoratedEntries) ) + // find all of the OverlayScenes do { // Starts from previously calculated scene and check if it is an OverlayScene @@ -69,7 +120,7 @@ public fun rememberSceneState( } // Keep added scenes to the end of our list until we find a non-overlay scene allScenes += - sceneStrategy.calculateSceneWithSinglePaneFallback(this, overlaidEntries) + sceneStrategy.calculateSceneWithSinglePaneFallback(scope, overlaidEntries) } } while (overlaidEntries != null) @@ -86,11 +137,10 @@ public fun rememberSceneState( val previousEntries = previousScene?.previousEntries if (!previousEntries.isNullOrEmpty()) { // If there are previous entries, add the scene from those entries to the front of - // the - // list + // the list previousScenes.add( - 0, - sceneStrategy.calculateSceneWithSinglePaneFallback(this, previousEntries), + index = 0, + sceneStrategy.calculateSceneWithSinglePaneFallback(scope, previousEntries), ) } } while (!previousEntries.isNullOrEmpty()) @@ -98,13 +148,9 @@ public fun rememberSceneState( // remove the currentScene from the list previousScenes.remove(currentScene) - return SceneState( - entries = decoratedEntries, - overlayScenes = overlayScenes, - currentScene = currentScene, - previousScenes = previousScenes, - ) + SceneState(decoratedEntries, overlayScenes, currentScene, previousScenes) } +} /** * Class for holding the state associated with a scene diff --git a/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SharedEntryInSceneNavEntryDecorator.kt b/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SharedEntryInSceneNavEntryDecorator.kt new file mode 100644 index 0000000000000..2af6abb6b9134 --- /dev/null +++ b/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/scene/SharedEntryInSceneNavEntryDecorator.kt @@ -0,0 +1,56 @@ +/* + * 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 androidx.navigation3.scene + +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.navigation3.runtime.NavEntryDecorator +import androidx.navigation3.ui.LocalNavAnimatedContentScope + +/** Returns a [SharedEntryInSceneNavEntryDecorator] that is remembered across recompositions. */ +@Composable +internal fun rememberSharedEntryInSceneNavEntryDecorator( + sharedTransitionScope: SharedTransitionScope +): SharedEntryInSceneNavEntryDecorator = + remember(sharedTransitionScope) { SharedEntryInSceneNavEntryDecorator(sharedTransitionScope) } + +/** + * A [NavEntryDecorator] that wraps each entry in a [Modifier.sharedElement] to allow nav displays + * to animate arbitrarily place entries in different places in the composable call hierarchy. + * + * This should be wrapped around the [SceneSetupNavEntryDecorator]. + */ +internal class SharedEntryInSceneNavEntryDecorator( + sharedTransitionScope: SharedTransitionScope +) : + NavEntryDecorator( + decorate = { entry -> + with(sharedTransitionScope) { + Box( + Modifier.sharedElement( + rememberSharedContentState(entry.contentKey), + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + ) + ) { + entry.Content() + } + } + } + ) diff --git a/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/ui/NavDisplay.kt b/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/ui/NavDisplay.kt index 8f76c877d8ede..3dd97bb1f9dcc 100644 --- a/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/ui/NavDisplay.kt +++ b/navigation3/navigation3-ui/src/commonMain/kotlin/androidx/navigation3/ui/NavDisplay.kt @@ -23,6 +23,7 @@ import androidx.collection.mutableObjectFloatMapOf import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform +import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.SizeTransform import androidx.compose.animation.core.SeekableTransitionState import androidx.compose.animation.core.animate @@ -168,6 +169,92 @@ public object NavDisplay { * [NavEntry]s. * @param entryProvider lambda used to construct each possible [NavEntry] * @sample androidx.navigation3.ui.samples.SceneNav + * @sample androidx.navigation3.ui.samples.SceneNavSharedElementSample + */ +@Deprecated( + message = "Deprecated in favor of NavDisplay that supports sharedTransitionScope", + level = DeprecationLevel.HIDDEN, +) +@Composable +public fun NavDisplay( + backStack: List, + modifier: Modifier = Modifier, + contentAlignment: Alignment = Alignment.TopStart, + onBack: () -> Unit = { + if (backStack is MutableList) { + backStack.removeLastOrNull() + } + }, + entryDecorators: List> = + listOf(rememberSaveableStateHolderNavEntryDecorator()), + sceneStrategy: SceneStrategy = SinglePaneSceneStrategy(), + sizeTransform: SizeTransform? = null, + transitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = + defaultTransitionSpec(), + popTransitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = + defaultPopTransitionSpec(), + predictivePopTransitionSpec: + AnimatedContentTransitionScope>.( + @NavigationEvent.SwipeEdge Int + ) -> ContentTransform = + defaultPredictivePopTransitionSpec(), + entryProvider: (key: T) -> NavEntry, +) { + NavDisplay( + backStack = backStack, + modifier = modifier, + contentAlignment = contentAlignment, + onBack = onBack, + entryDecorators = entryDecorators, + sceneStrategy = sceneStrategy, + sharedTransitionScope = null, + sizeTransform = sizeTransform, + transitionSpec = transitionSpec, + popTransitionSpec = popTransitionSpec, + predictivePopTransitionSpec = predictivePopTransitionSpec, + entryProvider = entryProvider, + ) +} + +/** + * A nav display that renders and animates between different [Scene]s, each of which can render one + * or more [NavEntry]s. + * + * The [Scene]s are calculated with the given [SceneStrategy], which may be an assembled delegated + * chain of [SceneStrategy]s. If no [Scene] is calculated, the fallback will be to a + * [SinglePaneSceneStrategy]. + * + * It is allowable for different [Scene]s to render the same [NavEntry]s, perhaps on some conditions + * as determined by the [sceneStrategy] based on window size, form factor, other arbitrary logic. + * + * If this happens, and these [Scene]s are rendered at the same time due to animation or predictive + * back, then the content for the [NavEntry] will only be rendered in the most recent [Scene] that + * is the target for being the current scene as determined by [sceneStrategy]. This enforces a + * unique invocation of each [NavEntry], even if it is displayable by two different [Scene]s. + * + * By default, AnimatedContent transitions are prioritized in this order: + * ``` + * transitioning [NavEntry.metadata] > current [Scene.metadata] > NavDisplay defaults + * ``` + * + * However, a [Scene.metadata] does have the ability to override [NavEntry.metadata]. Nevertheless, + * the final fallback will always be the NavDisplay's default transitions. + * + * @param backStack the collection of keys that represents the state that needs to be handled + * @param modifier the modifier to be applied to the layout. + * @param contentAlignment The [Alignment] of the [AnimatedContent] + * @param onBack a callback for handling system back press. By default, this pops a single item off + * of the given back stack if it is a [MutableList], otherwise you should provide this parameter. + * @param entryDecorators list of [NavEntryDecorator] to add information to the entry content + * @param sceneStrategy the [SceneStrategy] to determine which scene to render a list of entries. + * @param sharedTransitionScope the [SharedTransitionScope] to allow transitions between scenes. + * @param sizeTransform the [SizeTransform] for the [AnimatedContent]. + * @param transitionSpec Default [ContentTransform] when navigating to [NavEntry]s. + * @param popTransitionSpec Default [ContentTransform] when popping [NavEntry]s. + * @param predictivePopTransitionSpec Default [ContentTransform] when popping with predictive back + * [NavEntry]s. + * @param entryProvider lambda used to construct each possible [NavEntry] + * @sample androidx.navigation3.ui.samples.SceneNav * @sample androidx.navigation3.ui.samples.SceneNavSharedEntrySample * @sample androidx.navigation3.ui.samples.SceneNavSharedElementSample */ @@ -184,6 +271,7 @@ public fun NavDisplay( entryDecorators: List> = listOf(rememberSaveableStateHolderNavEntryDecorator()), sceneStrategy: SceneStrategy = SinglePaneSceneStrategy(), + sharedTransitionScope: SharedTransitionScope? = null, sizeTransform: SizeTransform? = null, transitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = defaultTransitionSpec(), @@ -208,6 +296,7 @@ public fun NavDisplay( NavDisplay( entries = entries, sceneStrategy = sceneStrategy, + sharedTransitionScope = sharedTransitionScope, modifier = modifier, contentAlignment = contentAlignment, sizeTransform = sizeTransform, @@ -264,12 +353,97 @@ public fun NavDisplay( * @sample androidx.navigation3.ui.samples.ConcatenatedBackStackSample * @see [rememberDecoratedNavEntries] */ +@Deprecated( + message = "Deprecated in favor of NavDisplay that supports sharedTransitionScope", + level = DeprecationLevel.HIDDEN, +) +@Composable +public fun NavDisplay( + entries: List>, + modifier: Modifier = Modifier, + contentAlignment: Alignment = Alignment.TopStart, + sceneStrategy: SceneStrategy = SinglePaneSceneStrategy(), + sizeTransform: SizeTransform? = null, + transitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = + defaultTransitionSpec(), + popTransitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = + defaultPopTransitionSpec(), + predictivePopTransitionSpec: + AnimatedContentTransitionScope>.( + @NavigationEvent.SwipeEdge Int + ) -> ContentTransform = + defaultPredictivePopTransitionSpec(), + onBack: () -> Unit, +) { + NavDisplay( + entries = entries, + sceneStrategy = sceneStrategy, + sharedTransitionScope = null, + modifier = modifier, + contentAlignment = contentAlignment, + sizeTransform = sizeTransform, + transitionSpec = transitionSpec, + popTransitionSpec = popTransitionSpec, + predictivePopTransitionSpec = predictivePopTransitionSpec, + onBack = onBack, + ) +} + +/** + * A nav display that renders and animates between different [Scene]s, each of which can render one + * or more [NavEntry]s. + * + * The [Scene]s are calculated with the given [SceneStrategy], which may be an assembled delegated + * chain of [SceneStrategy]s. If no [Scene] is calculated, the fallback will be to a + * [SinglePaneSceneStrategy]. + * + * It is allowable for different [Scene]s to render the same [NavEntry]s, perhaps on some conditions + * as determined by the [sceneStrategy] based on window size, form factor, other arbitrary logic. + * + * If this happens, and these [Scene]s are rendered at the same time due to animation or predictive + * back, then the content for the [NavEntry] will only be rendered in the most recent [Scene] that + * is the target for being the current scene as determined by [sceneStrategy]. This enforces a + * unique invocation of each [NavEntry], even if it is displayable by two different [Scene]s. + * + * By default, AnimatedContent transitions are prioritized in this order: + * ``` + * transitioning [NavEntry.metadata] > current [Scene.metadata] > NavDisplay defaults + * ``` + * + * However, a [Scene.metadata] does have the ability to override [NavEntry.metadata]. Nevertheless, + * the final fallback will always be the NavDisplay's default transitions. + * + * **WHEN TO USE** This overload can be used when you need to switch between different backStacks + * and each with their own separate decorator states, or when you want to concatenate backStacks and + * their states to form a larger backstack. + * + * **HOW TO USE** The [entries] can first be created via [rememberDecoratedNavEntries] in order to + * associate a backStack with a particular set of states. + * + * @param entries the list of [NavEntry] built from a backStack. The entries can be created from a + * backStack decorated with [NavEntryDecorator] via [rememberDecoratedNavEntries]. + * @param modifier the modifier to be applied to the layout. + * @param contentAlignment The [Alignment] of the [AnimatedContent] + * @param sceneStrategy the [SceneStrategy] to determine which scene to render a list of entries. + * @param sharedTransitionScope the [SharedTransitionScope] to allow transitions between scenes. + * @param sizeTransform the [SizeTransform] for the [AnimatedContent]. + * @param transitionSpec Default [ContentTransform] when navigating to [NavEntry]s. + * @param popTransitionSpec Default [ContentTransform] when popping [NavEntry]s. + * @param predictivePopTransitionSpec Default [ContentTransform] when popping with predictive back + * [NavEntry]s. + * @param onBack a callback for handling system back press. + * @sample androidx.navigation3.ui.samples.MultipleBackStackSample + * @sample androidx.navigation3.ui.samples.ConcatenatedBackStackSample + * @sample androidx.navigation3.ui.samples.SceneNavSharedEntrySample + * @see [rememberDecoratedNavEntries] + */ @Composable public fun NavDisplay( entries: List>, modifier: Modifier = Modifier, contentAlignment: Alignment = Alignment.TopStart, sceneStrategy: SceneStrategy = SinglePaneSceneStrategy(), + sharedTransitionScope: SharedTransitionScope? = null, sizeTransform: SizeTransform? = null, transitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = defaultTransitionSpec(), @@ -284,7 +458,7 @@ public fun NavDisplay( ) { require(entries.isNotEmpty()) { "NavDisplay entries cannot be empty" } - val sceneState = rememberSceneState(entries, sceneStrategy, onBack) + val sceneState = rememberSceneState(entries, sceneStrategy, sharedTransitionScope, onBack) val scene = sceneState.currentScene // Predictive Back Handling From e33c89074f85224e917016ba233479e5d079b9b6 Mon Sep 17 00:00:00 2001 From: "Oleksandr.Karpovich" Date: Fri, 5 Dec 2025 15:20:53 +0100 Subject: [PATCH 2/3] update Navigation3 redirection version to 1.1.0-alpha01 --- gradle.properties | 2 +- libraryversions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 50e8040ea423f..40ae7b5f4cf8c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -115,7 +115,7 @@ artifactRedirection.version.androidx.annotation=1.9.1 artifactRedirection.version.androidx.graphics=1.1.0-alpha01 artifactRedirection.version.androidx.lifecycle=2.10.0 artifactRedirection.version.androidx.navigation=2.9.1 -artifactRedirection.version.androidx.navigation3=1.0.0-rc01 +artifactRedirection.version.androidx.navigation3=1.1.0-alpha01 artifactRedirection.version.androidx.navigationevent=1.0.0-rc01 artifactRedirection.version.androidx.performance=1.0.0-alpha01 artifactRedirection.version.androidx.savedstate=1.4.0 diff --git a/libraryversions.toml b/libraryversions.toml index 6dea6fd7d241b..97ba48b01da98 100644 --- a/libraryversions.toml +++ b/libraryversions.toml @@ -96,7 +96,7 @@ MEDIA = "1.7.0-rc01" MEDIAROUTER = "1.8.0-alpha01" METRICS = "1.0.0-beta02" NAVIGATION = "2.9.1" -NAVIGATION3 = "1.0.0-rc01" +NAVIGATION3 = "1.1.0-alpha01" NAVIGATIONEVENT = "1.0.0-rc01" PAGING = "3.4.0-alpha01" PALETTE = "1.1.0-alpha01" From f9206042e052610573563724514784b5e6e6dfd5 Mon Sep 17 00:00:00 2001 From: "Oleksandr.Karpovich" Date: Fri, 5 Dec 2025 15:25:59 +0100 Subject: [PATCH 3/3] update Navigation3 api dump according to 1.1.0-alpha01 --- .../navigation3-ui/api/desktop/navigation3-ui.api | 9 ++++++--- navigation3/navigation3-ui/api/navigation3-ui.klib.api | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/navigation3/navigation3-ui/api/desktop/navigation3-ui.api b/navigation3/navigation3-ui/api/desktop/navigation3-ui.api index 6c3dfa1337156..d9de7815e31e2 100644 --- a/navigation3/navigation3-ui/api/desktop/navigation3-ui.api +++ b/navigation3/navigation3-ui/api/desktop/navigation3-ui.api @@ -49,7 +49,8 @@ public final class androidx/navigation3/scene/SceneState { } public final class androidx/navigation3/scene/SceneStateKt { - public static final fun rememberSceneState (Ljava/util/List;Landroidx/navigation3/scene/SceneStrategy;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Landroidx/navigation3/scene/SceneState; + public static final fun rememberSceneState (Ljava/util/List;Landroidx/navigation3/scene/SceneStrategy;Landroidx/compose/animation/SharedTransitionScope;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)Landroidx/navigation3/scene/SceneState; + public static final synthetic fun rememberSceneState (Ljava/util/List;Landroidx/navigation3/scene/SceneStrategy;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Landroidx/navigation3/scene/SceneState; } public abstract interface class androidx/navigation3/scene/SceneStrategy { @@ -83,8 +84,10 @@ public final class androidx/navigation3/ui/NavDisplay { public final class androidx/navigation3/ui/NavDisplayKt { public static final fun NavDisplay (Landroidx/navigation3/scene/SceneState;Landroidx/navigationevent/compose/NavigationEventState;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/compose/animation/SizeTransform;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V - public static final fun NavDisplay (Ljava/util/List;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/navigation3/scene/SceneStrategy;Landroidx/compose/animation/SizeTransform;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V - public static final fun NavDisplay (Ljava/util/List;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Lkotlin/jvm/functions/Function0;Ljava/util/List;Landroidx/navigation3/scene/SceneStrategy;Landroidx/compose/animation/SizeTransform;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V + public static final fun NavDisplay (Ljava/util/List;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/navigation3/scene/SceneStrategy;Landroidx/compose/animation/SharedTransitionScope;Landroidx/compose/animation/SizeTransform;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V + public static final synthetic fun NavDisplay (Ljava/util/List;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/navigation3/scene/SceneStrategy;Landroidx/compose/animation/SizeTransform;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V + public static final fun NavDisplay (Ljava/util/List;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Lkotlin/jvm/functions/Function0;Ljava/util/List;Landroidx/navigation3/scene/SceneStrategy;Landroidx/compose/animation/SharedTransitionScope;Landroidx/compose/animation/SizeTransform;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V + public static final synthetic fun NavDisplay (Ljava/util/List;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Lkotlin/jvm/functions/Function0;Ljava/util/List;Landroidx/navigation3/scene/SceneStrategy;Landroidx/compose/animation/SizeTransform;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V public static final fun defaultPopTransitionSpec ()Lkotlin/jvm/functions/Function1; public static final fun defaultPredictivePopTransitionSpec ()Lkotlin/jvm/functions/Function2; public static final fun defaultTransitionSpec ()Lkotlin/jvm/functions/Function1; diff --git a/navigation3/navigation3-ui/api/navigation3-ui.klib.api b/navigation3/navigation3-ui/api/navigation3-ui.klib.api index fc85643d84f2d..08f404bd542d5 100644 --- a/navigation3/navigation3-ui/api/navigation3-ui.klib.api +++ b/navigation3/navigation3-ui/api/navigation3-ui.klib.api @@ -93,9 +93,12 @@ final val androidx.navigation3.ui/LocalNavAnimatedContentScope // androidx.navig final fun (): androidx.compose.runtime/ProvidableCompositionLocal // androidx.navigation3.ui/LocalNavAnimatedContentScope.|(){}[0] final val androidx.navigation3.ui/androidx_navigation3_ui_NavDisplay$stableprop // androidx.navigation3.ui/androidx_navigation3_ui_NavDisplay$stableprop|#static{}androidx_navigation3_ui_NavDisplay$stableprop[0] +final fun <#A: kotlin/Any> androidx.navigation3.scene/rememberSceneState(kotlin.collections/List>, androidx.navigation3.scene/SceneStrategy<#A>, androidx.compose.animation/SharedTransitionScope?, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int): androidx.navigation3.scene/SceneState<#A> // androidx.navigation3.scene/rememberSceneState|rememberSceneState(kotlin.collections.List>;androidx.navigation3.scene.SceneStrategy<0:0>;androidx.compose.animation.SharedTransitionScope?;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.scene/rememberSceneState(kotlin.collections/List>, androidx.navigation3.scene/SceneStrategy<#A>, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int): androidx.navigation3.scene/SceneState<#A> // androidx.navigation3.scene/rememberSceneState|rememberSceneState(kotlin.collections.List>;androidx.navigation3.scene.SceneStrategy<0:0>;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(androidx.navigation3.scene/SceneState<#A>, androidx.navigationevent.compose/NavigationEventState>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(androidx.navigation3.scene.SceneState<0:0>;androidx.navigationevent.compose.NavigationEventState>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] +final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(kotlin.collections/List<#A>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, kotlin/Function0?, kotlin.collections/List>?, androidx.navigation3.scene/SceneStrategy<#A>?, androidx.compose.animation/SharedTransitionScope?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, kotlin/Function1<#A, androidx.navigation3.runtime/NavEntry<#A>>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(kotlin.collections.List<0:0>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;kotlin.Function0?;kotlin.collections.List>?;androidx.navigation3.scene.SceneStrategy<0:0>?;androidx.compose.animation.SharedTransitionScope?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;kotlin.Function1<0:0,androidx.navigation3.runtime.NavEntry<0:0>>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(kotlin.collections/List<#A>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, kotlin/Function0?, kotlin.collections/List>?, androidx.navigation3.scene/SceneStrategy<#A>?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, kotlin/Function1<#A, androidx.navigation3.runtime/NavEntry<#A>>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(kotlin.collections.List<0:0>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;kotlin.Function0?;kotlin.collections.List>?;androidx.navigation3.scene.SceneStrategy<0:0>?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;kotlin.Function1<0:0,androidx.navigation3.runtime.NavEntry<0:0>>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int;kotlin.Int){0§}[0] +final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(kotlin.collections/List>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, androidx.navigation3.scene/SceneStrategy<#A>?, androidx.compose.animation/SharedTransitionScope?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(kotlin.collections.List>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;androidx.navigation3.scene.SceneStrategy<0:0>?;androidx.compose.animation.SharedTransitionScope?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/NavDisplay(kotlin.collections/List>, androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, androidx.navigation3.scene/SceneStrategy<#A>?, androidx.compose.animation/SizeTransform?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function1>, androidx.compose.animation/ContentTransform>?, kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform>?, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.navigation3.ui/NavDisplay|NavDisplay(kotlin.collections.List>;androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;androidx.navigation3.scene.SceneStrategy<0:0>?;androidx.compose.animation.SizeTransform?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function1>,androidx.compose.animation.ContentTransform>?;kotlin.Function2>,kotlin.Int,androidx.compose.animation.ContentTransform>?;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/defaultPopTransitionSpec(): kotlin/Function1>, androidx.compose.animation/ContentTransform> // androidx.navigation3.ui/defaultPopTransitionSpec|defaultPopTransitionSpec(){0§}[0] final fun <#A: kotlin/Any> androidx.navigation3.ui/defaultPredictivePopTransitionSpec(): kotlin/Function2>, kotlin/Int, androidx.compose.animation/ContentTransform> // androidx.navigation3.ui/defaultPredictivePopTransitionSpec|defaultPredictivePopTransitionSpec(){0§}[0]