Repository: arkivanov/Decompose
Pull request #295 (2023-01-03 11:05:06)

Renamed backStackRecreationDepth to backStackCreateDepth


Patch:
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index bcfafced..0cbc4876 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -195,8 +195,8 @@ public final class com/arkivanov/decompose/router/stack/ChildStack {
 }
 
 public final class com/arkivanov/decompose/router/stack/ChildStackFactoryKt {
-	public static final fun childStack (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/stack/StackNavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZILkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
-	public static synthetic fun childStack$default (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/stack/StackNavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value;
+	public static final fun childStack (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/stack/StackNavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;IZLkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static synthetic fun childStack$default (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/stack/StackNavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;IZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value;
 }
 
 public abstract interface class com/arkivanov/decompose/router/stack/StackNavigation : com/arkivanov/decompose/router/stack/StackNavigationSource, com/arkivanov/decompose/router/stack/StackNavigator {
diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api
index 99b7ee91..07104d11 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -174,8 +174,8 @@ public final class com/arkivanov/decompose/router/stack/ChildStack {
 }
 
 public final class com/arkivanov/decompose/router/stack/ChildStackFactoryKt {
-	public static final fun childStack (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/stack/StackNavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZILkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
-	public static synthetic fun childStack$default (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/stack/StackNavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value;
+	public static final fun childStack (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/stack/StackNavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;IZLkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static synthetic fun childStack$default (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/stack/StackNavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;IZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value;
 }
 
 public abstract interface class com/arkivanov/decompose/router/stack/StackNavigation : com/arkivanov/decompose/router/stack/StackNavigationSource, com/arkivanov/decompose/router/stack/StackNavigator {
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt
index ef0a8c9c..5e54fb11 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt
@@ -24,13 +24,13 @@ import kotlin.reflect.KClass
  * if there is no saved state, must be not empty and unique.
  * @param configurationClass a [KClass] of the component configurations.
  * @param key a key of the stack, must be unique if there are multiple stacks in the same component.
+ * @param backStackCreateDepth automatically creates the specified amount of components
+ * in the back stack, e.g. when initialized, during navigation or after process death.
+ * Default value is `0`, which means that a component is only created when it becomes active.
+ * Use `1` to always create the previous component in the stack. Use [Int.MAX_VALUE] to
+ * create all components in the stack.
  * @param handleBackButton determines whether the overlay should be automatically dismissed
  * on back button press or not, default is `false`.
- * @param backStackRecreationDepth automatically recreates the specified amount of components
- * in the back stack after configuration change or process death (e.g. on Android).
- * Default value is `0`, which means that a component is only recreated when it becomes active.
- * Use `1` to always recreate the previous component in the stack. Use [Int.MAX_VALUE] to
- * recreate all components in the stack.
  * @param childFactory a factory function that creates new child instances.
  * @return an observable [Value] of [ChildStack].
  */
@@ -40,8 +40,8 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
     initialStack: () -> List<C>,
     configurationClass: KClass<out C>,
     key: String = "DefaultChildStack",
+    backStackCreateDepth: Int = 0,
     handleBackButton: Boolean = false,
-    backStackRecreationDepth: Int = 0,
     childFactory: (configuration: C, ComponentContext) -> T,
 ): Value<ChildStack<C, T>> =
     children(
@@ -53,9 +53,10 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
 
             StackNavState(
                 active = stack.last(),
-                backStack = stack.dropLast(1).map { configuration ->
-                    SimpleChildNavState(configuration = configuration, status = Status.DESTROYED)
-                },
+                backStack = stack
+                    .dropLast(1)
+                    .map { configuration -> SimpleChildNavState(configuration = configuration, status = Status.DESTROYED) }
+                    .withCreateDepth(createDepth = backStackCreateDepth),
             )
         },
         saveNavState = { navState ->
@@ -78,7 +79,7 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
                             status = Status.DESTROYED,
                         )
                     }
-                    .withRecreationDepth(recreationDepth = backStackRecreationDepth)
+                    .withCreateDepth(createDepth = backStackCreateDepth)
             )
         },
         navTransformer = { navState, event ->
@@ -102,7 +103,7 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
                             },
                         )
                     }
-                    .withRecreationDepth(recreationDepth = backStackRecreationDepth),
+                    .withCreateDepth(createDepth = backStackCreateDepth),
             )
         },
         onEventComplete = { event, newNavState, oldNavState ->
@@ -113,7 +114,7 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
                 {
                     StackNavState(
                         active = navState.backStack.last().configuration,
-                        backStack = navState.backStack.dropLast(1).withRecreationDepth(recreationDepth = backStackRecreationDepth),
+                        backStack = navState.backStack.dropLast(1).withCreateDepth(createDepth = backStackCreateDepth),
                     )
                 }
             } else {
@@ -130,11 +131,11 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
     )
 
 @ExperimentalDecomposeApi
-private fun <C : Any> List<SimpleChildNavState<C>>.withRecreationDepth(recreationDepth: Int): List<SimpleChildNavState<C>> =
-    if (isRecreationRequired(recreationDepth = recreationDepth)) {
+private fun <C : Any> List<SimpleChildNavState<C>>.withCreateDepth(createDepth: Int): List<SimpleChildNavState<C>> =
+    if (isCreateRequired(createDepth = createDepth)) {
         mapIndexed { index, item ->
             when (item.status) {
-                Status.DESTROYED -> if (lastIndex - index < recreationDepth) item.copy(status = Status.INACTIVE) else item
+                Status.DESTROYED -> if (lastIndex - index < createDepth) item.copy(status = Status.INACTIVE) else item
                 Status.INACTIVE,
                 Status.ACTIVE -> item
             }
@@ -144,8 +145,8 @@ private fun <C : Any> List<SimpleChildNavState<C>>.withRecreationDepth(recreatio
     }
 
 @ExperimentalDecomposeApi
-private fun List<SimpleChildNavState<*>>.isRecreationRequired(recreationDepth: Int): Boolean {
-    for (i in lastIndex downTo max(lastIndex - recreationDepth + 1, 0)) {
+private fun List<SimpleChildNavState<*>>.isCreateRequired(createDepth: Int): Boolean {
+    for (i in lastIndex downTo max(lastIndex - createDepth + 1, 0)) {
         if (get(i).status == Status.DESTROYED) {
             return true
         }
@@ -186,8 +187,8 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
     source: StackNavigationSource<C>,
     noinline initialStack: () -> List<C>,
     key: String = "DefaultChildStack",
+    backStackCreateDepth: Int = 0,
     handleBackButton: Boolean = false,
-    backStackRecreationDepth: Int = 0,
     noinline childFactory: (configuration: C, ComponentContext) -> T
 ): Value<ChildStack<C, T>> =
     childStack(
@@ -196,7 +197,7 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
         configurationClass = C::class,
         key = key,
         handleBackButton = handleBackButton,
-        backStackRecreationDepth = backStackRecreationDepth,
+        backStackCreateDepth = backStackCreateDepth,
         childFactory = childFactory,
     )
 
@@ -207,8 +208,8 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
     source: StackNavigationSource<C>,
     initialConfiguration: C,
     key: String = "DefaultChildStack",
+    backStackCreateDepth: Int = 0,
     handleBackButton: Boolean = false,
-    backStackRecreationDepth: Int = 0,
     noinline childFactory: (configuration: C, ComponentContext) -> T
 ): Value<ChildStack<C, T>> =
     childStack(
@@ -217,6 +218,6 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
         configurationClass = C::class,
         key = key,
         handleBackButton = handleBackButton,
-        backStackRecreationDepth = backStackRecreationDepth,
+        backStackCreateDepth = backStackCreateDepth,
         childFactory = childFactory,
     )
diff --git a/docs/navigation/stack/overview.md b/docs/navigation/stack/overview.md
index 20cac8a5..da30a966 100644
--- a/docs/navigation/stack/overview.md
+++ b/docs/navigation/stack/overview.md
@@ -132,11 +132,15 @@ class DefaultRootComponent(
 }
 ```
 
-## Configuration changes and process death on (not only) Android
+## Components in the back stack
+
+`Child Stack` can not be empty, there is always one active (resumed) child component. Components in the back stack are either stopped or destroyed. When `Child Stack` is first initialized or recreated after e.g. process death, only the active component is created, components in the back stack remain destroyed and are created on demand.
 
-`Child Stack` automatically preserves the stack when a configuration change or process death occurs. By default, only the active component is recreated, components in the back stack remain destroyed and are recreated on demand.
+Use the `backStackCreateDepth` argument to control how many components in the back stack are automatically created. E.g. use `1` to always create the last component in the back stack (in addition to the active component which is always created), use `Int.MAX_VALUE` to create all components in the back stack. Default value is `0`, which means that a component is only created when it becomes active. The automatic creation of components in the back stack may be useful with swipe-back navigation, e.g. in SwiftUI.
+
+## Configuration changes and process death on (not only) Android
 
-Use the `backStackRecreationDepth` argument to control how many components in the back stack are automatically recreated. E.g. use `1` to always recreate the last component in the back stack (in addition to the active component which is always recreated), use `Int.MAX_VALUE` to recreate all components in the back stack. Default value is `0`, which means that a component is only recreated when it becomes active. The automatic recreation of components in the back stack may be useful with swipe-back navigation, e.g. in SwiftUI.
+`Child Stack` automatically preserves the stack when a configuration change or process death occurs. By default, only the active component is recreated, components in the back stack remain destroyed and are created on demand.
 
 ## Delivering a result when navigating back
 
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/counters/DefaultCountersComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/counters/DefaultCountersComponent.kt
index bbaf8223..1e4152ee 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/counters/DefaultCountersComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/counters/DefaultCountersComponent.kt
@@ -18,7 +18,7 @@ internal class DefaultCountersComponent(
         childStack(
             source = navigation,
             initialConfiguration = Config(index = 0, isBackEnabled = false),
-            backStackRecreationDepth = Int.MAX_VALUE, // Useful for swipe navigation in SwiftUI after process death
+            backStackCreateDepth = Int.MAX_VALUE, // Useful for swipe navigation in SwiftUI after process death
             childFactory = ::child,
         )
 


Test patch:
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
index ff64f3b8..c7ee1e34 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackIntegrationTest.kt
@@ -11,6 +11,7 @@ import com.arkivanov.essenty.backhandler.BackCallback
 import com.arkivanov.essenty.backhandler.BackDispatcher
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
 import com.arkivanov.essenty.instancekeeper.getOrCreate
+import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import com.arkivanov.essenty.lifecycle.resume
 import com.arkivanov.essenty.parcelable.Parcelable
@@ -346,7 +347,19 @@ class ChildStackIntegrationTest {
     }
 
     @Test
-    fun GIVEN_back_stack_not_empty_and_backStackRecreationDepth_is_1_WHEN_recreated_THEN_last_to_children_recreated() {
+    fun GIVEN_backStackCreateDepth_WHEN_created_THEN_back_stack_created() {
+        val context = DefaultComponentContext(lifecycle = lifecycle)
+
+        val stack by context.childStack(
+            initialStack = listOf(Config(1), Config(2), Config(3), Config(4)),
+            backStackCreateDepth = 2,
+        )
+
+        stack.assertStack(1 to null, 2 to 2, 3 to 3, 4 to 4)
+    }
+
+    @Test
+    fun GIVEN_back_stack_not_empty_and_backStackCreateDepth_is_1_WHEN_recreated_THEN_last_to_children_recreated() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
         oldContext.childStack(initialStack = listOf(Config(1), Config(2), Config(3)))
@@ -354,13 +367,13 @@ class ChildStackIntegrationTest {
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newStack by newContext.childStack(initialStack = emptyList(), backStackRecreationDepth = 1)
+        val newStack by newContext.childStack(initialStack = emptyList(), backStackCreateDepth = 1)
 
         newStack.assertStack(1 to null, 2 to 2, 3 to 3)
     }
 
     @Test
-    fun GIVEN_back_stack_not_empty_and_backStackRecreationDepth_is_max_WHEN_recreated_THEN_all_children_recreated() {
+    fun GIVEN_back_stack_not_empty_and_backStackCreateDepth_is_max_WHEN_recreated_THEN_all_children_recreated() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
         oldContext.childStack(initialStack = listOf(Config(1), Config(2), Config(3)))
@@ -368,20 +381,20 @@ class ChildStackIntegrationTest {
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newStack by newContext.childStack(initialStack = emptyList(), backStackRecreationDepth = Int.MAX_VALUE)
+        val newStack by newContext.childStack(initialStack = emptyList(), backStackCreateDepth = Int.MAX_VALUE)
 
         newStack.assertStack(1 to 1, 2 to 2, 3 to 3)
     }
 
     @Test
-    fun GIVEN_back_stack_not_empty_and_backStackRecreationDepth_is_1_and_recreated_WHEN_pop_THEN_last_two_children_recreated() {
+    fun GIVEN_back_stack_not_empty_and_backStackCreateDepth_is_1_and_recreated_WHEN_pop_THEN_last_two_children_recreated() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
         oldContext.childStack(initialStack = listOf(Config(1), Config(2), Config(3), Config(4)))
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newStack by newContext.childStack(initialStack = emptyList(), backStackRecreationDepth = 1)
+        val newStack by newContext.childStack(initialStack = emptyList(), backStackCreateDepth = 1)
 
         navigation.pop()
 
@@ -389,14 +402,14 @@ class ChildStackIntegrationTest {
     }
 
     @Test
-    fun GIVEN_back_stack_not_empty_and_backStackRecreationDepth_is_1_and_recreated_WHEN_back_pressed_THEN_last_two_children_recreated() {
+    fun GIVEN_back_stack_not_empty_and_backStackCreateDepth_is_1_and_recreated_WHEN_back_pressed_THEN_last_two_children_recreated() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
         oldContext.childStack(initialStack = listOf(Config(1), Config(2), Config(3), Config(4)))
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, backHandler = backDispatcher)
-        val newStack by newContext.childStack(initialStack = emptyList(), backStackRecreationDepth = 1)
+        val newStack by newContext.childStack(initialStack = emptyList(), backStackCreateDepth = 1)
 
         backDispatcher.back()
 
@@ -404,9 +417,9 @@ class ChildStackIntegrationTest {
     }
 
     @Test
-    fun GIVEN_one_child_in_back_stack_and_backStackRecreationDepth_is_1_WHEN_pop_THEN_child_popped() {
+    fun GIVEN_one_child_in_back_stack_and_backStackCreateDepth_is_1_WHEN_pop_THEN_child_popped() {
         val context = DefaultComponentContext(lifecycle = lifecycle)
-        val stack by context.childStack(initialStack = listOf(Config(1), Config(2)), backStackRecreationDepth = 1)
+        val stack by context.childStack(initialStack = listOf(Config(1), Config(2)), backStackCreateDepth = 1)
 
         navigation.pop()
 
@@ -414,9 +427,9 @@ class ChildStackIntegrationTest {
     }
 
     @Test
-    fun GIVEN_one_child_in_back_stack_and_backStackRecreationDepth_is_2_WHEN_pop_THEN_child_popped() {
+    fun GIVEN_one_child_in_back_stack_and_backStackCreateDepth_is_2_WHEN_pop_THEN_child_popped() {
         val context = DefaultComponentContext(lifecycle = lifecycle)
-        val stack by context.childStack(initialStack = listOf(Config(1), Config(2)), backStackRecreationDepth = 2)
+        val stack by context.childStack(initialStack = listOf(Config(1), Config(2)), backStackCreateDepth = 2)
 
         navigation.pop()
 
@@ -424,9 +437,9 @@ class ChildStackIntegrationTest {
     }
 
     @Test
-    fun GIVEN_two_children_in_back_stack_and_backStackRecreationDepth_is_1_WHEN_pop_THEN_child_popped() {
+    fun GIVEN_two_children_in_back_stack_and_backStackCreateDepth_is_1_WHEN_pop_THEN_child_popped() {
         val context = DefaultComponentContext(lifecycle = lifecycle)
-        val stack by context.childStack(initialStack = listOf(Config(1), Config(2), Config(3)), backStackRecreationDepth = 1)
+        val stack by context.childStack(initialStack = listOf(Config(1), Config(2), Config(3)), backStackCreateDepth = 1)
 
         navigation.pop()
 
@@ -434,9 +447,9 @@ class ChildStackIntegrationTest {
     }
 
     @Test
-    fun GIVEN_two_children_in_back_stack_and_backStackRecreationDepth_is_2_WHEN_pop_THEN_child_popped() {
+    fun GIVEN_two_children_in_back_stack_and_backStackCreateDepth_is_2_WHEN_pop_THEN_child_popped() {
         val context = DefaultComponentContext(lifecycle = lifecycle)
-        val stack by context.childStack(initialStack = listOf(Config(1), Config(2), Config(3)), backStackRecreationDepth = 2)
+        val stack by context.childStack(initialStack = listOf(Config(1), Config(2), Config(3)), backStackCreateDepth = 2)
 
         navigation.pop()
 
@@ -445,13 +458,13 @@ class ChildStackIntegrationTest {
 
     private fun ComponentContext.childStack(
         initialStack: List<Config>,
-        backStackRecreationDepth: Int = 0,
+        backStackCreateDepth: Int = 0,
     ): Value<ChildStack<Config, Component>> =
         childStack(
             source = navigation,
             initialStack = { initialStack },
+            backStackCreateDepth = backStackCreateDepth,
             handleBackButton = true,
-            backStackRecreationDepth = backStackRecreationDepth,
             childFactory = ::Component,
         )
 
@@ -465,6 +478,14 @@ class ChildStackIntegrationTest {
 
     private fun ChildStack<Config, Component>.assertStack(vararg children: Pair<Int, Int?>) {
         assertEquals(children.toList(), this.children)
+        assertEquals(Lifecycle.State.RESUMED, active.instance.lifecycle.state)
+
+        assertEquals(
+            children
+                .dropLast(1)
+                .map { (_, instanceId) -> if (instanceId == null) Lifecycle.State.DESTROYED else Lifecycle.State.CREATED },
+            backStack.map { it.instance?.lifecycle?.state ?: Lifecycle.State.DESTROYED },
+        )
     }
 
     @Parcelize