Repository: arkivanov/Decompose
Pull request #351 (2023-03-28 22:59:05)

Always recreate components in Child Stack


Patch:
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index f1b5fcbb..b4a01fc0 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -199,10 +199,10 @@ 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/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;IZLkotlin/jvm/functions/Function2;)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;ZIZLkotlin/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/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;IZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)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;ZIZLkotlin/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/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)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;ZZLkotlin/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/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)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;ZZLkotlin/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 549be786..421d91c1 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -177,10 +177,10 @@ 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/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;IZLkotlin/jvm/functions/Function2;)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;ZIZLkotlin/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/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;IZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)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;ZIZLkotlin/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/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)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;ZZLkotlin/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/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)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;ZZLkotlin/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/children/ChildrenNavigator.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
index 29307f4c..b65e43b4 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
@@ -48,7 +48,7 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
         }
 
         lifecycle.doOnDestroy {
-            items.forEach { item ->
+            items.asReversed().forEach { item ->
                 when (item) {
                     is Created -> {
                         item.backHandler.stop()
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt
index f0275485..d2313ed1 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt
@@ -8,7 +8,7 @@ import com.arkivanov.decompose.GettingList
  */
 data class ChildStack<out C : Any, out T : Any>(
     val active: Child.Created<C, T>,
-    val backStack: List<Child<C, T>> = emptyList(),
+    val backStack: List<Child.Created<C, T>> = emptyList(),
 ) {
 
     constructor(configuration: C, instance: T) : this(
@@ -21,7 +21,7 @@ data class ChildStack<out C : Any, out T : Any>(
     /**
      * Returns the full stack of component configurations, ordered from tail to head.
      */
-    val items: List<Child<C, T>> =
+    val items: List<Child.Created<C, T>> =
         GettingList(size = backStack.size + 1) { index ->
             backStack.getOrNull(index) ?: active
         }
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 93e8d812..8d11352b 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
@@ -2,7 +2,6 @@ package com.arkivanov.decompose.router.stack
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.GettingList
 import com.arkivanov.decompose.router.children.ChildNavState.Status
 import com.arkivanov.decompose.router.children.NavState
 import com.arkivanov.decompose.router.children.SimpleChildNavState
@@ -12,7 +11,6 @@ import com.arkivanov.essenty.parcelable.Parcelable
 import com.arkivanov.essenty.parcelable.ParcelableContainer
 import com.arkivanov.essenty.parcelable.Parcelize
 import com.arkivanov.essenty.parcelable.consumeRequired
-import kotlin.math.max
 import kotlin.reflect.KClass
 
 /**
@@ -25,11 +23,6 @@ import kotlin.reflect.KClass
  * @param key a key of the stack, must be unique if there are multiple stacks in the same component.
  * @param persistent determines whether the navigation state should pre preserved or not,
  * default is `true`.
- * @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 childFactory a factory function that creates new child instances.
@@ -41,7 +34,6 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
     configurationClass: KClass<out C>,
     key: String = "DefaultChildStack",
     persistent: Boolean = true,
-    backStackCreateDepth: Int = 0,
     handleBackButton: Boolean = false,
     childFactory: (configuration: C, ComponentContext) -> T,
 ): Value<ChildStack<C, T>> =
@@ -66,7 +58,6 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
                 .map { it.consumeRequired(configurationClass) }
         },
         key = key,
-        backStackCreateDepth = backStackCreateDepth,
         handleBackButton = handleBackButton,
         childFactory = childFactory,
     )
@@ -83,11 +74,6 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
  * If `null` is returned then [initialStack] is used instead.
  * The restored stack must have the same amount of configurations and in the same order.
  * @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 childFactory a factory function that creates new child instances.
@@ -99,67 +85,31 @@ fun <C : Any, T : Any> ComponentContext.childStack(
     saveStack: (List<C>) -> ParcelableContainer?,
     restoreStack: (ParcelableContainer) -> List<C>?,
     key: String = "DefaultChildStack",
-    backStackCreateDepth: Int = 0,
     handleBackButton: Boolean = false,
     childFactory: (configuration: C, ComponentContext) -> T,
 ): Value<ChildStack<C, T>> =
     children(
         source = source,
         key = key,
-        initialState = {
-            StackNavState(
-                configurations = initialStack(),
-                backStackCreateDepth = backStackCreateDepth,
-            )
-        },
+        initialState = { StackNavState(configurations = initialStack()) },
         saveState = { saveStack(it.configurations) },
-        restoreState = { container ->
-            StackNavState(
-                configurations = restoreStack(container) ?: initialStack(),
-                backStackCreateDepth = backStackCreateDepth,
-            )
-        },
-        navTransformer = { state, event ->
-            val newStack = event.transformer(state.configurations)
-            check(newStack.isNotEmpty()) { "Configuration stack must not be empty" }
-
-            val oldStatuses = state.children.associateBy(keySelector = { it.configuration }, valueTransform = { it.status })
-
-            StackNavState(
-                active = newStack.last(),
-                backStack = newStack
-                    .dropLast(1)
-                    .map { configuration ->
-                        SimpleChildNavState(
-                            configuration = configuration,
-                            status = when (oldStatuses[configuration]) {
-                                Status.DESTROYED -> Status.DESTROYED
-                                Status.INACTIVE -> Status.INACTIVE
-                                Status.ACTIVE -> Status.INACTIVE
-                                null -> Status.DESTROYED
-                            },
-                        )
-                    }
-                    .withCreateDepth(createDepth = backStackCreateDepth),
-            )
-        },
+        restoreState = { container -> StackNavState(configurations = restoreStack(container) ?: initialStack()) },
+        navTransformer = { state, event -> StackNavState(configurations = event.transformer(state.configurations)) },
         stateMapper = { _, children ->
+            @Suppress("UNCHECKED_CAST")
+            val createdChildren = children as List<Child.Created<C, T>>
+
             ChildStack(
-                active = children.last() as Child.Created,
-                backStack = children.dropLast(1),
+                active = createdChildren.last(),
+                backStack = createdChildren.dropLast(1),
             )
         },
         onEventComplete = { event, newState, oldState ->
             event.onComplete(newState.configurations, oldState.configurations)
         },
         backTransformer = { state ->
-            if (handleBackButton && state.backStack.isNotEmpty()) {
-                {
-                    StackNavState(
-                        active = state.backStack.last().configuration,
-                        backStack = state.backStack.dropLast(1).withCreateDepth(createDepth = backStackCreateDepth),
-                    )
-                }
+            if (handleBackButton && (state.configurations.size > 1)) {
+                { StackNavState(configurations = state.configurations.dropLast(1)) }
             } else {
                 null
             }
@@ -175,7 +125,6 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
     noinline initialStack: () -> List<C>,
     key: String = "DefaultChildStack",
     persistent: Boolean = true,
-    backStackCreateDepth: Int = 0,
     handleBackButton: Boolean = false,
     noinline childFactory: (configuration: C, ComponentContext) -> T
 ): Value<ChildStack<C, T>> =
@@ -186,7 +135,6 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
         key = key,
         persistent = persistent,
         handleBackButton = handleBackButton,
-        backStackCreateDepth = backStackCreateDepth,
         childFactory = childFactory,
     )
 
@@ -198,7 +146,6 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
     initialConfiguration: C,
     key: String = "DefaultChildStack",
     persistent: Boolean = true,
-    backStackCreateDepth: Int = 0,
     handleBackButton: Boolean = false,
     noinline childFactory: (configuration: C, ComponentContext) -> T
 ): Value<ChildStack<C, T>> =
@@ -209,63 +156,26 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
         key = key,
         persistent = persistent,
         handleBackButton = handleBackButton,
-        backStackCreateDepth = backStackCreateDepth,
         childFactory = childFactory,
     )
 
-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 < createDepth) item.copy(status = Status.INACTIVE) else item
-                Status.INACTIVE,
-                Status.ACTIVE -> item
-            }
-        }
-    } else {
-        this
-    }
-
-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
-        }
-    }
-
-    return false
-}
-
 private data class StackNavState<out C : Any>(
-    val active: C,
-    val backStack: List<SimpleChildNavState<C>>,
+    val configurations: List<C>,
 ) : NavState<C> {
 
-    private val activeChildNavState = SimpleChildNavState(configuration = active, status = Status.ACTIVE)
+    init {
+        require(configurations.isNotEmpty()) { "Configuration stack must not be empty" }
+    }
 
     override val children: List<SimpleChildNavState<C>> =
-        GettingList(size = backStack.size + 1) { index ->
-            backStack.getOrNull(index) ?: activeChildNavState
-        }
-
-    val configurations: List<C> =
-        GettingList(size = children.size) { index ->
-            children[index].configuration
+        configurations.mapIndexed { index, configuration ->
+            SimpleChildNavState(
+                configuration = configuration,
+                status = if (index == configurations.lastIndex) Status.ACTIVE else Status.INACTIVE,
+            )
         }
 }
 
-private fun <C : Any> StackNavState(configurations: List<C>, backStackCreateDepth: Int): StackNavState<C> {
-    require(configurations.isNotEmpty()) { "Configuration stack must not be empty" }
-
-    return StackNavState(
-        active = configurations.last(),
-        backStack = configurations
-            .dropLast(1)
-            .map { SimpleChildNavState(configuration = it, status = Status.DESTROYED) }
-            .withCreateDepth(createDepth = backStackCreateDepth),
-    )
-}
-
 @Parcelize
 private class StackSavedNavState(
     val configurations: List<ParcelableContainer>,
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ValueExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ValueExt.kt
index 3facff29..9fa00770 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ValueExt.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ValueExt.kt
@@ -5,6 +5,6 @@ import com.arkivanov.decompose.value.Value
 
 val <C : Any, T : Any> Value<ChildStack<C, T>>.active: Child.Created<C, T> get() = value.active
 
-val <C : Any, T : Any> Value<ChildStack<C, T>>.backStack: List<Child<C, T>> get() = value.backStack
+val <C : Any, T : Any> Value<ChildStack<C, T>>.backStack: List<Child.Created<C, T>> get() = value.backStack
 
-val <C : Any, T : Any> Value<ChildStack<C, T>>.items: List<Child<C, T>> get() = value.items
+val <C : Any, T : Any> Value<ChildStack<C, T>>.items: List<Child.Created<C, T>> get() = value.items
diff --git a/docs/navigation/stack/overview.md b/docs/navigation/stack/overview.md
index 2fc60aa2..128c5bb5 100644
--- a/docs/navigation/stack/overview.md
+++ b/docs/navigation/stack/overview.md
@@ -134,15 +134,15 @@ class DefaultRootComponent(
 
 ## 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.
-
-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.
+`Child Stack` can not be empty, there is always one active (resumed) child component. Components in the back stack are always stopped. When `Child Stack` is first initialized or recreated after e.g. process death, all components are automatically created and the active component is resumed.
 
 ## Screen recreation and process death on (not only) Android
 
-`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.
+`Child Stack` automatically preserves the stack when a configuration change or process death occurs. Use the `persistent` argument to disable stack preservation completely. When disabled, the stack is reset to the initial state when recreated.
+
+Components are created in their order. E.g. the first component in the back stack is created first, then the next component in the back stack is created, and so on. The active component is the latest component created.
 
-Use the `persistent` argument to disable stack preservation completely. When disabled, the stack is reset to the initial state when a configuration change or process death occurs.
+Components are destroyed in reverse order, e.g. the active component is destroyed first, then the last component in the back stack is destroyed, and so on.
 
 ## 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 eba793c0..cb5d286e 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
@@ -22,7 +22,6 @@ internal class DefaultCountersComponent(
         childStack(
             source = navigation,
             initialConfiguration = Config(index = 0, isBackEnabled = false),
-            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/children/ChildrenBasicTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
index f199c16f..189c0a07 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
@@ -1,8 +1,12 @@
 package com.arkivanov.decompose.router.children
 
+import com.arkivanov.decompose.DefaultComponentContext
 import com.arkivanov.decompose.router.children.ChildNavState.Status.ACTIVE
 import com.arkivanov.decompose.router.children.ChildNavState.Status.DESTROYED
 import com.arkivanov.decompose.router.children.ChildNavState.Status.INACTIVE
+import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
+import com.arkivanov.essenty.lifecycle.destroy
+import com.arkivanov.essenty.lifecycle.doOnDestroy
 import kotlin.test.Test
 import kotlin.test.assertContentEquals
 
@@ -62,4 +66,49 @@ internal class ChildrenBasicTest : ChildrenTestBase() {
 
         assertContentEquals(listOf("onStateChanged", "onEventComplete"), results)
     }
+
+    @Test
+    fun WHEN_destroyed_THEN_components_destroyed_in_reversed_order() {
+        val destroyEvents = ArrayList<Int>()
+
+        context.children(initialState = stateOf(1 by INACTIVE, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
+            componentContext.lifecycle.doOnDestroy { destroyEvents += config.id }
+            Component(config = config, componentContext = componentContext)
+        }
+
+        lifecycle.destroy()
+
+        assertContentEquals(listOf(3, 2, 1), destroyEvents)
+    }
+
+    @Test
+    fun WHEN_created_THEN_components_created_in_order() {
+        val createEvents = ArrayList<Int>()
+
+        context.children(initialState = stateOf(1 by INACTIVE, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
+            createEvents += config.id
+            Component(config = config, componentContext = componentContext)
+        }
+
+        assertContentEquals(listOf(1, 2, 3), createEvents)
+    }
+
+    @Test
+    fun WHEN_recreated_THEN_components_created_in_original_order() {
+        val createEvents = ArrayList<Int>()
+
+        val oldStateKeeper = TestStateKeeperDispatcher()
+        val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
+        oldContext.children(initialState = stateOf(1 by INACTIVE, 2 by INACTIVE, 3 by ACTIVE))
+
+        val savedState = oldStateKeeper.save()
+        val newStateKeeper = TestStateKeeperDispatcher(savedState)
+        val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
+        newContext.children { config, componentContext ->
+            createEvents += config.id
+            Component(config = config, componentContext = componentContext)
+        }
+
+        assertContentEquals(listOf(1, 2, 3), createEvents)
+    }
 }
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 4dd77cc7..46c8cec8 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
@@ -74,7 +74,7 @@ class ChildStackIntegrationTest {
 
         navigation.pop()
 
-        stack.assertStack(1 to null, 2 to 2)
+        stack.assertStack(1 to 1, 2 to 2)
     }
 
     @Test
@@ -94,7 +94,7 @@ class ChildStackIntegrationTest {
 
         backDispatcher.back()
 
-        stack.assertStack(1 to null, 2 to 2)
+        stack.assertStack(1 to 1, 2 to 2)
     }
 
     @Test
@@ -103,7 +103,7 @@ class ChildStackIntegrationTest {
 
         navigation.replaceCurrent(Config(3))
 
-        stack.assertStack(1 to null, 3 to 3)
+        stack.assertStack(1 to 1, 3 to 3)
     }
 
     @Test
@@ -112,7 +112,7 @@ class ChildStackIntegrationTest {
 
         navigation.navigate { it.reversed() }
 
-        stack.assertStack(3 to 3, 2 to null, 1 to 1)
+        stack.assertStack(3 to 3, 2 to 2, 1 to 1)
     }
 
     @Test
@@ -188,7 +188,7 @@ class ChildStackIntegrationTest {
 
         backDispatcher.back()
 
-        stack.assertStack(1 to null, 2 to 2)
+        stack.assertStack(1 to 1, 2 to 2)
     }
 
     @Test
@@ -223,7 +223,7 @@ class ChildStackIntegrationTest {
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
         val newStack by newContext.childStack(initialStack = emptyList())
 
-        newStack.assertStack(1 to null, 2 to 2)
+        newStack.assertStack(1 to 1, 2 to 2)
     }
 
     @Test
@@ -381,7 +381,7 @@ class ChildStackIntegrationTest {
     }
 
     @Test
-    fun GIVEN_another_child_pushed_WHEN_recreated_THEN_instance_destroyed_for_previous_child() {
+    fun GIVEN_another_child_pushed_WHEN_recreated_THEN_instance_not_destroyed_for_previous_child() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
@@ -394,164 +394,31 @@ class ChildStackIntegrationTest {
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, instanceKeeper = instanceKeeper)
         newContext.childStack(initialStack = emptyList())
 
-        assertTrue(instance.isDestroyed)
-    }
-
-    @Test
-    fun GIVEN_back_stack_not_empty_WHEN_recreated_THEN_only_active_child_recreated() {
-        val oldStateKeeper = TestStateKeeperDispatcher()
-        val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        oldContext.childStack(initialStack = listOf(Config(1), Config(2)))
-
-        val savedState = oldStateKeeper.save()
-        val newStateKeeper = TestStateKeeperDispatcher(savedState)
-        val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newStack by newContext.childStack(initialStack = emptyList())
-
-        newStack.assertStack(1 to null, 2 to 2)
-    }
-
-    @Test
-    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)))
-
-        val savedState = oldStateKeeper.save()
-        val newStateKeeper = TestStateKeeperDispatcher(savedState)
-        val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        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_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)))
-
-        val savedState = oldStateKeeper.save()
-        val newStateKeeper = TestStateKeeperDispatcher(savedState)
-        val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        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_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(), backStackCreateDepth = 1)
-
-        navigation.pop()
-
-        newStack.assertStack(1 to null, 2 to 2, 3 to 3)
-    }
-
-    @Test
-    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(), backStackCreateDepth = 1)
-
-        backDispatcher.back()
-
-        newStack.assertStack(1 to null, 2 to 2, 3 to 3)
-    }
-
-    @Test
-    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)), backStackCreateDepth = 1)
-
-        navigation.pop()
-
-        stack.assertStack(1 to 1)
-    }
-
-    @Test
-    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)), backStackCreateDepth = 2)
-
-        navigation.pop()
-
-        stack.assertStack(1 to 1)
-    }
-
-    @Test
-    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)), backStackCreateDepth = 1)
-
-        navigation.pop()
-
-        stack.assertStack(1 to 1, 2 to 2)
-    }
-
-    @Test
-    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)), backStackCreateDepth = 2)
-
-        navigation.pop()
-
-        stack.assertStack(1 to 1, 2 to 2)
+        assertFalse(instance.isDestroyed)
     }
 
     private fun ComponentContext.childStack(
         initialStack: List<Config>,
         persistent: Boolean = true,
-        backStackCreateDepth: Int = 0,
     ): Value<ChildStack<Config, Component>> =
         childStack(
             source = navigation,
             initialStack = { initialStack },
             persistent = persistent,
-            backStackCreateDepth = backStackCreateDepth,
             handleBackButton = true,
             childFactory = ::Component,
         )
 
-    private val ChildStack<Config, Component>.children: List<Pair<Int, Int?>>
-        get() = items.map { child ->
-            when (child) {
-                is Child.Created -> child.configuration.id to child.instance.id
-                is Child.Destroyed -> child.configuration.id to null
-            }
-        }
+    private val ChildStack<Config, Component>.children: List<Pair<Int, Int>>
+        get() = items.map { child -> child.configuration.id to child.instance.id }
 
-    private fun ChildStack<Config, Component>.assertStack(vararg children: Pair<Int, Int?>) {
+    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 },
+            List(children.size - 1) { Lifecycle.State.CREATED },
+            backStack.map { it.instance.lifecycle.state },
         )
     }
 
diff --git a/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenTest.kt b/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenTest.kt
index d3270a6b..d1f0d4ea 100644
--- a/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenTest.kt
+++ b/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenTest.kt
@@ -144,7 +144,7 @@ class ChildrenTest(
     private fun routerState(activeConfig: Config, backstack: List<Config> = emptyList()): ChildStack<Config, Config> =
         ChildStack(
             active = Child.Created(configuration = activeConfig, instance = activeConfig),
-            backStack = backstack.map { Child.Destroyed(configuration = it) },
+            backStack = backstack.map { Child.Created(configuration = it, instance = it) },
         )
 
     @Composable
diff --git a/extensions-compose-jetpack/src/androidTest/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/ChildrenTest.kt b/extensions-compose-jetpack/src/androidTest/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/ChildrenTest.kt
index 0f14665d..e1a7ea87 100644
--- a/extensions-compose-jetpack/src/androidTest/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/ChildrenTest.kt
+++ b/extensions-compose-jetpack/src/androidTest/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/ChildrenTest.kt
@@ -128,7 +128,7 @@ class ChildrenTest(
     private fun routerState(activeConfig: Config, backstack: List<Config> = emptyList()): ChildStack<Config, Config> =
         ChildStack(
             active = Child.Created(configuration = activeConfig, instance = activeConfig),
-            backStack = backstack.map { Child.Destroyed(configuration = it) }
+            backStack = backstack.map { Child.Created(configuration = it, instance = it) }
         )
 
     @Composable