Repository: arkivanov/Decompose
Pull request #313 (2023-01-31 22:03:39)

Stabilized generic navigation API
- Added `onNavStateChanged` callback
- Renamed some arguments
- Removed `ExperimentalDecomposeApi` annotations
- Updated docs


Patch:
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index e4ed461c..08634a31 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -88,7 +88,8 @@ public final class com/arkivanov/decompose/router/children/ChildNavState$Status
 }
 
 public final class com/arkivanov/decompose/router/children/ChildrenFactoryKt {
-	public static final fun children (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static final fun children (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static synthetic fun children$default (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value;
 }
 
 public abstract interface class com/arkivanov/decompose/router/children/NavState {
diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api
index 61b7006e..08c21db6 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -74,7 +74,8 @@ public final class com/arkivanov/decompose/router/children/ChildNavState$Status
 }
 
 public final class com/arkivanov/decompose/router/children/ChildrenFactoryKt {
-	public static final fun children (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static final fun children (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static synthetic fun children$default (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value;
 }
 
 public abstract interface class com/arkivanov/decompose/router/children/NavState {
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt
index fc91e951..c506aeb4 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt
@@ -1,11 +1,8 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
 /**
  * Represents a child navigation state.
  */
-@ExperimentalDecomposeApi
 interface ChildNavState<out C : Any> {
 
     /**
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
index 6ad7056a..28e5f4be 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
@@ -2,7 +2,6 @@ package com.arkivanov.decompose.router.children
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.backhandler.child
 import com.arkivanov.decompose.value.MutableValue
 import com.arkivanov.decompose.value.Value
@@ -39,35 +38,37 @@ import com.arkivanov.essenty.statekeeper.consume
  * @param source a source of navigation events.
  * @param key a key of this `children` collection, must be unique if there are multiple
  * `children` used in the same component.
- * @param initialNavState an initial navigation state that should be used if there is no previously saved state.
- * @param saveNavState a function that saves the provided navigation state into [ParcelableContainer].
+ * @param initialState an initial navigation state that should be used if there is no previously saved state.
+ * @param saveState a function that saves the provided navigation state into [ParcelableContainer].
  * The navigation state is not saved if `null` is returned.
- * @param restoreNavState a function that restores the navigation state from the provided [ParcelableContainer].
- * If `null` is returned then [initialNavState] is used instead.
+ * @param restoreState a function that restores the navigation state from the provided [ParcelableContainer].
+ * If `null` is returned then [initialState] is used instead.
  * The restored navigation state must have the same amount of child configurations and in the same order.
  * The restored child [Statuses][ChildNavState.Status] can be any, e.g. a previously active child may become
  * destroyed, etc.
  * @param navTransformer a function that transforms the current navigation state to a new one using the provided
  * navigation event.
+ * @param stateMapper combines the provided navigation state and list of child components to a resulting state.
+ * @param onStateChanged called every time the navigation state changes, `oldState` is `null` when
+ * called first time during initialisation.
  * @param onEventComplete called when a navigation event is processed and the navigation completed.
  * @param backTransformer a function that checks the provided navigation state, and either returns another function
  * transforming the navigation state to a new one, or `null` if back button handling should be disabled. Called
- * during the initialisation and after each navigation event.
- * @param stateMapper combines the provided navigation state and list of child components to a resulting state.
+ * during initialisation and after each navigation event.
  * @param childFactory a factory function that creates new child component instances.
  * @return an observable [Value] of the resulting children state.
  */
-@ExperimentalDecomposeApi
 fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.children(
     source: NavigationSource<E>,
     key: String,
-    initialNavState: () -> N,
-    saveNavState: (navState: N) -> ParcelableContainer?,
-    restoreNavState: (container: ParcelableContainer) -> N?,
-    navTransformer: (navState: N, event: E) -> N,
-    onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit,
-    backTransformer: (navState: N) -> (() -> N)?,
-    stateMapper: (navState: N, children: List<Child<C, T>>) -> S,
+    initialState: () -> N,
+    saveState: (state: N) -> ParcelableContainer?,
+    restoreState: (container: ParcelableContainer) -> N?,
+    navTransformer: (state: N, event: E) -> N,
+    stateMapper: (state: N, children: List<Child<C, T>>) -> S,
+    onStateChanged: (newState: N, oldState: N?) -> Unit = { _, _ -> },
+    onEventComplete: (event: E, newState: N, oldState: N) -> Unit = { _, _, _ -> },
+    backTransformer: (state: N) -> (() -> N)? = { null },
     childFactory: (configuration: C, componentContext: ComponentContext) -> T,
 ): Value<S> {
     val mainBackHandler = backHandler.child()
@@ -82,15 +83,15 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
                     backHandler = backHandler.child(),
                     childFactory = childFactory,
                 ),
-                navState = savedState?.navState?.let(restoreNavState) ?: initialNavState(),
+                navState = savedState?.navState?.let(restoreState) ?: initialState(),
                 savedChildState = savedState?.childState,
             )
         }
 
     stateKeeper.register(key = key) {
-        saveNavState(navigator.navState)?.let { savedNavState ->
+        saveState(navigator.navState)?.let { savedState ->
             SavedState(
-                navState = savedNavState,
+                navState = savedState,
                 childState = navigator.saveChildState(),
             )
         }
@@ -102,17 +103,20 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
 
     lateinit var backCallback: BackCallback
 
-    fun onAfterNavigate(navState: N) {
-        bt = backTransformer(navState)
+    fun onAfterNavigate(newState: N, oldState: N) {
+        bt = backTransformer(newState)
         backCallback.isEnabled = bt != null
-        state.value = stateMapper(navState, navigator.children)
+        state.value = stateMapper(newState, navigator.children)
+        onStateChanged(newState, oldState)
     }
 
     backCallback =
         BackCallback(isEnabled = bt != null) {
-            bt?.invoke()?.also { navState ->
-                navigator.navigate(navState = navState)
-                onAfterNavigate(navState)
+            bt?.invoke()?.also { state ->
+                val oldState = navigator.navState
+                navigator.navigate(navState = state)
+                val newState = navigator.navState
+                onAfterNavigate(newState, oldState)
             }
         }
 
@@ -120,11 +124,11 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
 
     val eventObserver: (E) -> Unit =
         { event ->
-            val oldNavState = navigator.navState
+            val oldState = navigator.navState
             navigator.navigate(navState = navTransformer(navigator.navState, event))
-            val newNavState = navigator.navState
-            onAfterNavigate(newNavState)
-            onEventComplete(event, newNavState, oldNavState)
+            val newState = navigator.navState
+            onAfterNavigate(newState, oldState)
+            onEventComplete(event, newState, oldState)
         }
 
     source.subscribe(eventObserver)
@@ -135,6 +139,8 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
         mainBackHandler.unregister(backCallback)
     }
 
+    onStateChanged(navigator.navState, null)
+
     return state
 }
 
@@ -143,27 +149,28 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
  * so it's automatically saved and restored. This method can be used if the custom save/restore logic
  * is not required.
  */
-@ExperimentalDecomposeApi
 inline fun <C : Parcelable, T : Any, E : Any, reified N, S : Any> ComponentContext.children(
     source: NavigationSource<E>,
     key: String,
-    noinline initialNavState: () -> N,
-    noinline navTransformer: (navState: N, event: E) -> N,
-    noinline onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit,
-    noinline backTransformer: (navState: N) -> (() -> N)?,
-    noinline stateMapper: (navState: N, children: List<Child<C, T>>) -> S,
+    noinline initialState: () -> N,
+    noinline navTransformer: (state: N, event: E) -> N,
+    noinline stateMapper: (state: N, children: List<Child<C, T>>) -> S,
+    noinline onStateChanged: (newState: N, oldState: N?) -> Unit = { _, _ -> },
+    noinline onEventComplete: (event: E, newState: N, oldState: N) -> Unit = { _, _, _ -> },
+    noinline backTransformer: (state: N) -> (() -> N)? = { null },
     noinline childFactory: (configuration: C, componentContext: ComponentContext) -> T,
 ): Value<S> where N : NavState<C>, N : Parcelable =
     children(
         source = source,
         key = key,
-        initialNavState = initialNavState,
-        saveNavState = { ParcelableContainer(it) },
-        restoreNavState = { it.consumeRequired(N::class) },
+        initialState = initialState,
+        saveState = { ParcelableContainer(it) },
+        restoreState = { it.consumeRequired(N::class) },
         navTransformer = navTransformer,
+        stateMapper = stateMapper,
+        onStateChanged = onStateChanged,
         onEventComplete = onEventComplete,
         backTransformer = backTransformer,
-        stateMapper = stateMapper,
         childFactory = childFactory,
     )
 
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 709791bf..29307f4c 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
@@ -1,7 +1,6 @@
 package com.arkivanov.decompose.router.children
 
 import com.arkivanov.decompose.Child
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.children.ChildItem.Created
 import com.arkivanov.decompose.router.children.ChildItem.Created.*
 import com.arkivanov.decompose.router.children.ChildNavState.Status
@@ -14,7 +13,6 @@ import com.arkivanov.essenty.lifecycle.resume
 import com.arkivanov.essenty.lifecycle.stop
 import com.arkivanov.essenty.parcelable.ParcelableContainer
 
-@OptIn(ExperimentalDecomposeApi::class)
 internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
     lifecycle: Lifecycle,
     retainedInstanceSupplier: (factory: () -> InstanceKeeper.Instance) -> InstanceKeeper.Instance,
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt
index 3b5a1f17..b69198cc 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt
@@ -1,11 +1,8 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
 /**
  * Represents an entire navigation state.
  */
-@ExperimentalDecomposeApi
 interface NavState<out C : Any> {
 
     /**
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleChildNavState.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleChildNavState.kt
index f77453a3..b0673402 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleChildNavState.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleChildNavState.kt
@@ -1,11 +1,8 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
 /**
  * A simple implementation of the [ChildNavState] interface.
  */
-@ExperimentalDecomposeApi
 data class SimpleChildNavState<out C : Any>(
     override val configuration: C,
     override val status: ChildNavState.Status,
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt
index 1ee4ec6e..a2e5b710 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt
@@ -1,13 +1,11 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.Relay
 
 /**
  * A simple implementation of the [NavigationSource] interface.
  * Broadcasts navigation events to every subscribed observer.
  */
-@ExperimentalDecomposeApi
 class SimpleNavigation<T : Any> : NavigationSource<T> {
 
     private val relay = Relay<T>()
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt
index 21ccaa4c..673705e9 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt
@@ -2,7 +2,6 @@ package com.arkivanov.decompose.router.overlay
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.children.ChildNavState.Status
 import com.arkivanov.decompose.router.children.NavState
 import com.arkivanov.decompose.router.children.SimpleChildNavState
@@ -27,7 +26,6 @@ import kotlin.reflect.KClass
  * @param childFactory a factory function that creates new child instances.
  * @return an observable [Value] of [ChildOverlay].
  */
-@OptIn(ExperimentalDecomposeApi::class)
 fun <C : Parcelable, T : Any> ComponentContext.childOverlay(
     source: OverlayNavigationSource<C>,
     configurationClass: KClass<out C>,
@@ -61,7 +59,6 @@ fun <C : Parcelable, T : Any> ComponentContext.childOverlay(
  * @param childFactory a factory function that creates new child instances.
  * @return an observable [Value] of [ChildOverlay].
  */
-@ExperimentalDecomposeApi
 fun <C : Any, T : Any> ComponentContext.childOverlay(
     source: OverlayNavigationSource<C>,
     saveConfiguration: (C?) -> ParcelableContainer?,
@@ -74,19 +71,19 @@ fun <C : Any, T : Any> ComponentContext.childOverlay(
     children(
         source = source,
         key = key,
-        initialNavState = { OverlayNavState(configuration = initialConfiguration()) },
-        saveNavState = { saveConfiguration(it.configuration) },
-        restoreNavState = { OverlayNavState(restoreConfiguration(it)) },
-        navTransformer = { navState, event -> OverlayNavState(configuration = event.transformer(navState.configuration)) },
-        onEventComplete = { event, newNavState, oldNavState -> event.onComplete(newNavState.configuration, oldNavState.configuration) },
-        backTransformer = { navState ->
-            if (handleBackButton && (navState.configuration != null)) {
+        initialState = { OverlayNavState(configuration = initialConfiguration()) },
+        saveState = { saveConfiguration(it.configuration) },
+        restoreState = { OverlayNavState(restoreConfiguration(it)) },
+        navTransformer = { state, event -> OverlayNavState(configuration = event.transformer(state.configuration)) },
+        stateMapper = { _, children -> ChildOverlay(overlay = children.firstOrNull() as? Child.Created?) },
+        onEventComplete = { event, newState, oldState -> event.onComplete(newState.configuration, oldState.configuration) },
+        backTransformer = { state ->
+            if (handleBackButton && (state.configuration != null)) {
                 { OverlayNavState(configuration = null) }
             } else {
                 null
             }
         },
-        stateMapper = { _, children -> ChildOverlay(overlay = children.firstOrNull() as? Child.Created?) },
         childFactory = childFactory,
     )
 
@@ -111,7 +108,6 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childOverlay(
         childFactory = childFactory,
     )
 
-@OptIn(ExperimentalDecomposeApi::class)
 private data class OverlayNavState<out C : Any>(
     val configuration: C?,
 ) : NavState<C> {
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 c200d23c..93e8d812 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.ExperimentalDecomposeApi
 import com.arkivanov.decompose.GettingList
 import com.arkivanov.decompose.router.children.ChildNavState.Status
 import com.arkivanov.decompose.router.children.NavState
@@ -94,7 +93,6 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
  * @param childFactory a factory function that creates new child instances.
  * @return an observable [Value] of [ChildStack].
  */
-@OptIn(ExperimentalDecomposeApi::class)
 fun <C : Any, T : Any> ComponentContext.childStack(
     source: StackNavigationSource<C>,
     initialStack: () -> List<C>,
@@ -108,24 +106,24 @@ fun <C : Any, T : Any> ComponentContext.childStack(
     children(
         source = source,
         key = key,
-        initialNavState = {
+        initialState = {
             StackNavState(
                 configurations = initialStack(),
                 backStackCreateDepth = backStackCreateDepth,
             )
         },
-        saveNavState = { saveStack(it.configurations) },
-        restoreNavState = { container ->
+        saveState = { saveStack(it.configurations) },
+        restoreState = { container ->
             StackNavState(
                 configurations = restoreStack(container) ?: initialStack(),
                 backStackCreateDepth = backStackCreateDepth,
             )
         },
-        navTransformer = { navState, event ->
-            val newStack = event.transformer(navState.configurations)
+        navTransformer = { state, event ->
+            val newStack = event.transformer(state.configurations)
             check(newStack.isNotEmpty()) { "Configuration stack must not be empty" }
 
-            val oldStatuses = navState.children.associateBy(keySelector = { it.configuration }, valueTransform = { it.status })
+            val oldStatuses = state.children.associateBy(keySelector = { it.configuration }, valueTransform = { it.status })
 
             StackNavState(
                 active = newStack.last(),
@@ -145,27 +143,27 @@ fun <C : Any, T : Any> ComponentContext.childStack(
                     .withCreateDepth(createDepth = backStackCreateDepth),
             )
         },
-        onEventComplete = { event, newNavState, oldNavState ->
-            event.onComplete(newNavState.configurations, oldNavState.configurations)
+        stateMapper = { _, children ->
+            ChildStack(
+                active = children.last() as Child.Created,
+                backStack = children.dropLast(1),
+            )
+        },
+        onEventComplete = { event, newState, oldState ->
+            event.onComplete(newState.configurations, oldState.configurations)
         },
-        backTransformer = { navState ->
-            if (handleBackButton && navState.backStack.isNotEmpty()) {
+        backTransformer = { state ->
+            if (handleBackButton && state.backStack.isNotEmpty()) {
                 {
                     StackNavState(
-                        active = navState.backStack.last().configuration,
-                        backStack = navState.backStack.dropLast(1).withCreateDepth(createDepth = backStackCreateDepth),
+                        active = state.backStack.last().configuration,
+                        backStack = state.backStack.dropLast(1).withCreateDepth(createDepth = backStackCreateDepth),
                     )
                 }
             } else {
                 null
             }
         },
-        stateMapper = { _, children ->
-            ChildStack(
-                active = children.last() as Child.Created,
-                backStack = children.dropLast(1),
-            )
-        },
         childFactory = childFactory,
     )
 
@@ -215,7 +213,6 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
         childFactory = childFactory,
     )
 
-@ExperimentalDecomposeApi
 private fun <C : Any> List<SimpleChildNavState<C>>.withCreateDepth(createDepth: Int): List<SimpleChildNavState<C>> =
     if (isCreateRequired(createDepth = createDepth)) {
         mapIndexed { index, item ->
@@ -229,7 +226,6 @@ private fun <C : Any> List<SimpleChildNavState<C>>.withCreateDepth(createDepth:
         this
     }
 
-@ExperimentalDecomposeApi
 private fun List<SimpleChildNavState<*>>.isCreateRequired(createDepth: Int): Boolean {
     for (i in lastIndex downTo max(lastIndex - createDepth + 1, 0)) {
         if (get(i).status == Status.DESTROYED) {
@@ -240,7 +236,6 @@ private fun List<SimpleChildNavState<*>>.isCreateRequired(createDepth: Int): Boo
     return false
 }
 
-@OptIn(ExperimentalDecomposeApi::class)
 private data class StackNavState<out C : Any>(
     val active: C,
     val backStack: List<SimpleChildNavState<C>>,
@@ -259,7 +254,6 @@ private data class StackNavState<out C : Any>(
         }
 }
 
-@ExperimentalDecomposeApi
 private fun <C : Any> StackNavState(configurations: List<C>, backStackCreateDepth: Int): StackNavState<C> {
     require(configurations.isNotEmpty()) { "Configuration stack must not be empty" }
 
diff --git a/docs/navigation/children/overview.md b/docs/navigation/children/overview.md
index 15ea0261..20a2421f 100644
--- a/docs/navigation/children/overview.md
+++ b/docs/navigation/children/overview.md
@@ -30,13 +30,14 @@ Using the `Generic Navigation` is pretty similar to any other navigation model,
 fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.children(
     source: NavigationSource<E>,
     key: String,
-    initialNavState: () -> N,
-    saveNavState: (navState: N) -> ParcelableContainer,
-    restoreNavState: (container: ParcelableContainer) -> N,
-    navTransformer: (navState: N, event: E) -> N,
-    onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit,
-    backTransformer: (navState: N) -> (() -> N)?,
-    stateMapper: (navState: N, children: List<Child<C, T>>) -> S,
+    initialState: () -> N,
+    saveState: (state: N) -> ParcelableContainer?,
+    restoreState: (container: ParcelableContainer) -> N?,
+    navTransformer: (state: N, event: E) -> N,
+    stateMapper: (state: N, children: List<Child<C, T>>) -> S,
+    onStateChanged: (newState: N, oldState: N?) -> Unit = { _, _ -> },
+    onEventComplete: (event: E, newState: N, oldState: N) -> Unit = { _, _, _ -> },
+    backTransformer: (state: N) -> (() -> N)? = { null },
     childFactory: (configuration: C, componentContext: ComponentContext) -> T,
 ): Value<S>
 ```
@@ -53,13 +54,14 @@ The `children` function accepts the following arguments:
 
 - `source: NavigationSource<E>` - an observable source of navigation events, the `Generic Navigation` subscribes to the source and performs the navigation. The [SimpleNavigation](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt) class can be used in simple cases when custom implementation is not required.
 - `key: String` - a key of the navigation, must be unique if there are multiple `children` used in the same component.
-- `initialNavState: () -> N` - an initial navigation state that should be used if there is no previously saved state.
-- `saveNavState: (navState: N) -> ParcelableContainer` - a function that saves the provided navigation state into `ParcelableContainer`, called when the hosting component goes to background.
-- `restoreNavState: (container: ParcelableContainer) -> N` - a function that restores the navigation state from the provided `ParcelableContainer`. The restored navigation state must have the same amount of child configurations and in the same order. The restored child `Statuses` can be any, e.g. a previously active child may become destroyed, etc.
-- `navTransformer: (navState: N, event: E) -> N` - a function that transforms the current navigation state to a new one using the provided navigation event. The implementation diffs both navigation states and manipulates child components as needed.
-- `onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit` - called when a navigation event is processed and the navigation completed.
-- `backTransformer: (navState: N) -> (() -> N)?` - a function that checks the provided navigation state, and either returns another function transforming the navigation state to a new one, or `null` if back button handling should be disabled. Called during the initialisation and after each navigation event.
-- `stateMapper: (navState: N, children: List<Child<C, T>>) -> S` - combines the provided navigation state and list of child components to a resulting custom state.
+- `initialState: () -> N` - an initial navigation state that should be used if there is no previously saved state.
+- `saveState: (state: N) -> ParcelableContainer` - a function that saves the provided navigation state into `ParcelableContainer`, called when the hosting component goes to background.
+- `restoreState: (container: ParcelableContainer) -> N` - a function that restores the navigation state from the provided `ParcelableContainer`. The restored navigation state must have the same amount of child configurations and in the same order. The restored child `Statuses` can be any, e.g. a previously active child may become destroyed, etc.
+- `navTransformer: (state: N, event: E) -> N` - a function that transforms the current navigation state to a new one using the provided navigation event. The implementation diffs both navigation states and manipulates child components as needed.
+- `stateMapper: (state: N, children: List<Child<C, T>>) -> S` - combines the provided navigation state and list of child components to a resulting custom state.
+- `onStateChanged: (newState: N, oldState: N?) -> Unit` - called every time the navigation state changes, `oldState` is `null` when called first time during initialisation. 
+- `onEventComplete: (event: E, newState: N, oldState: N) -> Unit` - called when a navigation event is processed and the navigation completed.
+- `backTransformer: (state: N) -> (() -> N)?` - a function that checks the provided navigation state, and either returns another function transforming the navigation state to a new one, or `null` if back button handling should be disabled. Called during the initialisation and after each navigation event.
 - `childFactory: (configuration: C, componentContext: ComponentContext) -> T` - childFactory a factory function that creates new child component instances.
 
 The `children` function returns an observable `Value` of the resulting children state.
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/DefaultCustomNavigationComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/DefaultCustomNavigationComponent.kt
index 75b43592..bb1e43fe 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/DefaultCustomNavigationComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/DefaultCustomNavigationComponent.kt
@@ -2,7 +2,6 @@ package com.arkivanov.sample.shared.customnavigation
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.children.ChildNavState
 import com.arkivanov.decompose.router.children.NavState
 import com.arkivanov.decompose.router.children.SimpleChildNavState
@@ -15,7 +14,6 @@ import com.arkivanov.sample.shared.customnavigation.CustomNavigationComponent.Ch
 import com.arkivanov.sample.shared.customnavigation.CustomNavigationComponent.Mode
 import com.arkivanov.sample.shared.customnavigation.KittenComponent.ImageType
 
-@OptIn(ExperimentalDecomposeApi::class)
 class DefaultCustomNavigationComponent(
     componentContext: ComponentContext,
 ) : CustomNavigationComponent, ComponentContext by componentContext {
@@ -26,7 +24,7 @@ class DefaultCustomNavigationComponent(
         children(
             source = navigation,
             key = "carousel",
-            initialNavState = {
+            initialState = {
                 NavigationState(
                     configurations = ImageType.values().map { imageType ->
                         Config(imageType = imageType)
@@ -35,20 +33,19 @@ class DefaultCustomNavigationComponent(
                     mode = Mode.CAROUSEL,
                 )
             },
-            navTransformer = { navState, transformer -> transformer(navState) },
-            onEventComplete = { _, _, _ -> },
-            backTransformer = {
-                it.takeIf { it.index > 0 }?.let { navState ->
-                    { navState.copy(index = navState.index - 1) }
-                }
-            },
-            stateMapper = { navState, children ->
+            navTransformer = { state, transformer -> transformer(state) },
+            stateMapper = { state, children ->
                 Children(
                     items = children.map { it as Child.Created },
-                    index = navState.index,
-                    mode = navState.mode,
+                    index = state.index,
+                    mode = state.mode,
                 )
             },
+            backTransformer = {
+                it.takeIf { it.index > 0 }?.let { state ->
+                    { state.copy(index = state.index - 1) }
+                }
+            },
             childFactory = { config, componentContext ->
                 DefaultKittenComponent(
                     componentContext = componentContext,
@@ -60,32 +57,32 @@ class DefaultCustomNavigationComponent(
     override val children: Value<Children<*, KittenComponent>> = _children
 
     override fun onSwitchToPagerClicked() {
-        navigation.navigate { navState ->
-            navState.copy(mode = Mode.PAGER)
+        navigation.navigate { state ->
+            state.copy(mode = Mode.PAGER)
         }
     }
 
     override fun onSwitchToCarouselClicked() {
-        navigation.navigate { navState ->
-            navState.copy(mode = Mode.CAROUSEL)
+        navigation.navigate { state ->
+            state.copy(mode = Mode.CAROUSEL)
         }
     }
 
     override fun onForwardClicked() {
-        navigation.navigate { navState ->
-            navState.copy(index = (navState.index + 1) % navState.configurations.size)
+        navigation.navigate { state ->
+            state.copy(index = (state.index + 1) % state.configurations.size)
         }
     }
 
     override fun onBackwardClicked() {
-        navigation.navigate { navState ->
-            navState.copy(index = (navState.index - 1) % navState.configurations.size)
+        navigation.navigate { state ->
+            state.copy(index = (state.index - 1) % state.configurations.size)
         }
     }
 
     override fun onShuffleClicked() {
-        navigation.navigate { navState ->
-            navState.copy(configurations = navState.configurations.shuffled())
+        navigation.navigate { state ->
+            state.copy(configurations = state.configurations.shuffled())
         }
     }
 


Test patch:
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
index 99f07c90..eaf56b1d 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
@@ -16,7 +16,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_children_created_with_active_child_and_enabled_callback_registered_WHEN_back_THEN_callback_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
         backDispatcher.back()
@@ -27,7 +27,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_children_created_with_active_child_and_disabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = false) { isCalled = true })
 
         backDispatcher.back()
@@ -38,7 +38,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_children_created_with_inactive_child_and_enabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
         backDispatcher.back()
@@ -49,7 +49,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_children_created_with_inactive_child_and_disabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = false) { isCalled = true })
 
         backDispatcher.back()
@@ -60,7 +60,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_destroyed_to_inactive_and_enabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         navigate { listOf(1 by INACTIVE, 2 by INACTIVE, 3 by ACTIVE) }
         children.getById(id = 1).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
@@ -72,7 +72,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_destroyed_to_active_and_enabled_callback_registered_WHEN_back_THEN_callback_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         navigate { listOf(1 by ACTIVE, 2 by INACTIVE, 3 by ACTIVE) }
         children.getById(id = 1).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
@@ -84,7 +84,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_inactive_to_active_and_enabled_callback_registered_WHEN_back_THEN_callback_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         navigate { listOf(1 by DESTROYED, 2 by ACTIVE, 3 by ACTIVE) }
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
@@ -96,7 +96,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_active_to_inactive_and_enabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
@@ -108,7 +108,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_enabled_callback_registered_in_inactive_child_and_switched_to_active_WHEN_back_THEN_callback_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
         navigate { listOf(1 by DESTROYED, 2 by ACTIVE, 3 by ACTIVE) }
 
@@ -120,7 +120,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_enabled_callback_registered_in_inactive_child_and_switched_to_destroyed_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
         navigate { listOf(1 by DESTROYED, 2 by DESTROYED, 3 by ACTIVE) }
 
@@ -132,7 +132,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_enabled_callback_registered_in_active_child_and_switched_to_inactive_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
 
@@ -144,7 +144,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_enabled_callback_registered_in_active_child_and_switched_to_destroyed_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
 
@@ -156,7 +156,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_backTransformer_returns_null_WHEN_back_THEN_returned_false() {
         context.children(
-            initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
             backTransformer = { null },
         )
 
@@ -168,8 +168,8 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_backTransformer_returns_not_null_WHEN_back_THEN_navigated() {
         val children by context.children(
-            initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
-            backTransformer = { { emptyList() } },
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            backTransformer = { { stateOf() } },
         )
 
         backDispatcher.back()
@@ -180,10 +180,10 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_navigated_and_backTransformer_returns_null_WHEN_back_THEN_returned_false() {
         context.children(
-            initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
-            backTransformer = { navState ->
-                if (navState.isNotEmpty()) {
-                    { emptyList() }
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            backTransformer = { state ->
+                if (state.children.isNotEmpty()) {
+                    { stateOf() }
                 } else {
                     null
                 }
@@ -195,4 +195,19 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
 
         assertFalse(result)
     }
+
+    @Test
+    fun WHEN_back_THEN_onStateChanged_called() {
+        val results = ArrayList<Pair<TestNavState, TestNavState?>>()
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            backTransformer = { { stateOf() } },
+            onStateChanged = { newState, oldState -> results += newState to oldState },
+        )
+        results.clear()
+
+        backDispatcher.back()
+
+        assertContentEquals(listOf(stateOf() to stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)), results)
+    }
 }
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 a9a7566c..f199c16f 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,6 +1,8 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.router.children.ChildNavState.Status.*
+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 kotlin.test.Test
 import kotlin.test.assertContentEquals
 
@@ -8,15 +10,56 @@ import kotlin.test.assertContentEquals
 internal class ChildrenBasicTest : ChildrenTestBase() {
 
     @Test
-    fun WHEN_navigate_THEN_onComplete_called() {
-        val results = ArrayList<Pair<List<SimpleChildNavState<Config>>, List<SimpleChildNavState<Config>>>>()
-        context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+    fun WHEN_navigate_THEN_onEventComplete_called() {
+        val results = ArrayList<Pair<TestNavState, TestNavState>>()
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            onEventComplete = { _, newState, oldState -> results += newState to oldState },
+        )
+
+        navigate { emptyList() }
+
+        assertContentEquals(listOf(stateOf() to stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)), results)
+    }
+
+    @Test
+    fun WHEN_created_THEN_onStateChanged_called() {
+        val results = ArrayList<Pair<TestNavState, TestNavState?>>()
+
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            onStateChanged = { newState, oldState -> results += newState to oldState },
+        )
+
+        assertContentEquals(listOf(stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE) to null), results)
+    }
 
-        navigate(transformer = { emptyList() }, onComplete = { newNavState, oldNavState -> results += newNavState to oldNavState })
+    @Test
+    fun WHEN_navigate_THEN_onStateChanged_called() {
+        val results = ArrayList<Pair<TestNavState, TestNavState?>>()
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            onStateChanged = { newState, oldState -> results += newState to oldState },
+        )
+        results.clear()
 
-        assertContentEquals(
-            listOf(emptyList<SimpleChildNavState<Config>>() to listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)),
-            results,
+        navigate { emptyList() }
+
+        assertContentEquals(listOf(stateOf() to stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)), results)
+    }
+
+    @Test
+    fun WHEN_navigate_THEN_onEventComplete_called_after_onStateChanged_called() {
+        val results = ArrayList<String>()
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            onEventComplete = { _, _, _ -> results += "onEventComplete" },
+            onStateChanged = { _, _ -> results += "onStateChanged" },
         )
+        results.clear()
+
+        navigate { emptyList() }
+
+        assertContentEquals(listOf("onStateChanged", "onEventComplete"), results)
     }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
index 7b763c70..ffad7849 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
@@ -15,14 +15,14 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_created_THEN_initial_state() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         children.assertChildren(1 to null, 2 to 2, 3 to 3)
     }
 
     @Test
     fun WHEN_created_THEN_lifecycles_correct() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         assertEquals(Lifecycle.State.CREATED, children.getById(id = 2).requireInstance().lifecycle.state)
         assertEquals(Lifecycle.State.RESUMED, children.getById(id = 3).requireInstance().lifecycle.state)
@@ -53,54 +53,54 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_destroyed_child_removed_THEN_state_updated() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 1 } }
+        navigate { state -> state.filterNot { it.configuration.id == 1 } }
 
         children.assertChildren(2 to 2, 3 to 3)
     }
 
     @Test
     fun WHEN_inactive_child_removed_THEN_state_updated() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 2 } }
+        navigate { state -> state.filterNot { it.configuration.id == 2 } }
 
         children.assertChildren(1 to null, 3 to 3)
     }
 
     @Test
     fun WHEN_inactive_child_removed_THEN_lifecycle_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 2).requireInstance()
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 2 } }
+        navigate { state -> state.filterNot { it.configuration.id == 2 } }
 
         assertEquals(Lifecycle.State.DESTROYED, component.lifecycle.state)
     }
 
     @Test
     fun WHEN_active_child_removed_THEN_state_updated() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 3 } }
+        navigate { state -> state.filterNot { it.configuration.id == 3 } }
 
         children.assertChildren(1 to null, 2 to 2)
     }
 
     @Test
     fun WHEN_active_child_removed_THEN_lifecycle_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 3).requireInstance()
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 3 } }
+        navigate { state -> state.filterNot { it.configuration.id == 3 } }
 
         assertEquals(Lifecycle.State.DESTROYED, component.lifecycle.state)
     }
 
     @Test
     fun WHEN_all_children_removed_THEN_state_updated() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         navigate { emptyList() }
 
@@ -109,7 +109,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_all_children_removed_THEN_lifecycles_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component2 = children.getById(id = 2).requireInstance()
         val component3 = children.getById(id = 3).requireInstance()
 
@@ -121,7 +121,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_destroyed_to_inactive_THEN_lifecycle_created() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         navigate { listOf(1 by INACTIVE, 2 by INACTIVE, 3 by ACTIVE) }
 
@@ -130,7 +130,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_active_THEN_lifecycle_resumed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         navigate { listOf(1 by DESTROYED, 2 by ACTIVE, 3 by ACTIVE) }
 
@@ -139,7 +139,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_destroyed_THEN_lifecycle_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 2).requireInstance()
 
         navigate { listOf(1 by DESTROYED, 2 by DESTROYED, 3 by ACTIVE) }
@@ -149,7 +149,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_inactive_THEN_lifecycle_stopped() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
 
@@ -158,7 +158,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_destroyed_THEN_lifecycle_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 3).requireInstance()
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
@@ -168,7 +168,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_context_lifecycle_stopped_THEN_all_component_lifecycles_stopped() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         lifecycle.stop()
 
@@ -178,7 +178,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_context_lifecycle_destroyed_THEN_all_component_lifecycles_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component2 = children.getById(id = 2).requireInstance()
         val component3 = children.getById(id = 3).requireInstance()
 
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
index 7bf0789b..e486b297 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
@@ -20,7 +20,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_destroyed_THEN_instance_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 2).requireInstance()
         val instance = component.instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
@@ -31,7 +31,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_destroyed_THEN_instance_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 3).requireInstance()
         val instance = component.instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
@@ -42,7 +42,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_inactive_THEN_instance_not_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val instance = children.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
@@ -52,7 +52,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun GIVEN_child_switched_from_inactive_to_destroyed_WHEN_child_switched_to_inactive_THEN_instance_not_retained() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val oldInstance = children.getById(id = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
         navigate { listOf(1 by DESTROYED, 2 by DESTROYED, 3 by ACTIVE) }
 
@@ -64,7 +64,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun GIVEN_child_switched_from_active_to_destroyed_WHEN_child_switched_to_active_THEN_instance_not_retained() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val oldInstance = children.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
 
@@ -79,7 +79,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val oldInstance = oldChildren.getById(id = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
@@ -96,7 +96,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val instance = oldChildren.getById(id = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
@@ -112,7 +112,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val oldInstance = oldChildren.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
@@ -129,7 +129,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val instance = oldChildren.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
@@ -145,13 +145,13 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val instance = oldChildren.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, instanceKeeper = instanceKeeper)
-        newContext.children(restoreNavState = { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
+        newContext.children(restoreState = { stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
 
         assertTrue(instance.isDestroyed)
     }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
index 078bb286..b281db7b 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
@@ -1,7 +1,6 @@
 package com.arkivanov.decompose.router.children
 
 import com.arkivanov.decompose.DefaultComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 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
@@ -16,13 +15,12 @@ import kotlin.test.assertFalse
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Suppress("TestFunctionName")
 internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_inactive_THEN_state_saved() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         var isCalled = false
         children.getById(id = 3).requireInstance().stateKeeper.register(key = "key") {
             isCalled = true
@@ -36,7 +34,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_destroyed_THEN_state_saved() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         var isCalled = false
         children.getById(id = 3).requireInstance().stateKeeper.register(key = "key") {
             isCalled = true
@@ -50,7 +48,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_destroyed_THEN_state_not_saved() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         var isCalled = false
         children.getById(id = 2).requireInstance().stateKeeper.register(key = "key") {
             isCalled = true
@@ -64,7 +62,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_active_THEN_state_not_saved() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         var isCalled = false
         children.getById(id = 2).requireInstance().stateKeeper.register(key = "key") {
             isCalled = true
@@ -79,7 +77,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_destroyed_to_inactive_THEN_state_not_saved() {
         var isCalled = false
-        context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
+        context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
             val component = Component(config, componentContext)
             if (config.id == 1) {
                 component.stateKeeper.register(key = "key") {
@@ -98,7 +96,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_destroyed_to_active_THEN_state_not_saved() {
         var isCalled = false
-        context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
+        context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
             val component = Component(config, componentContext)
             if (config.id == 1) {
                 component.stateKeeper.register(key = "key") {
@@ -116,7 +114,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun GIVEN_child_switched_from_active_to_destroyed_WHEN_child_switched_to_active_THEN_state_restored() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
 
@@ -128,7 +126,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun GIVEN_child_switched_from_active_to_destroyed_WHEN_child_switched_to_inactive_THEN_state_restored() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
 
@@ -142,7 +140,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_saving_state_WHEN_recreated_THEN_navigation_state_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
@@ -156,12 +154,12 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_not_saving_state_WHEN_recreated_THEN_navigation_state_not_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE), saveNavState = { null })
+        oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE), saveState = { null })
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(initialNavState = listOf(1 by ACTIVE))
+        val newChildren by newContext.children(initialState = stateOf(1 by ACTIVE))
 
         newChildren.assertChildren(1 to 1)
     }
@@ -170,12 +168,12 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_not_restoring_state_WHEN_recreated_THEN_navigation_state_not_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(initialNavState = listOf(1 by ACTIVE), restoreNavState = { null })
+        val newChildren by newContext.children(initialState = stateOf(1 by ACTIVE), restoreState = { null })
 
         newChildren.assertChildren(1 to 1)
     }
@@ -184,7 +182,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_saving_state_WHEN_recreated_THEN_child_states_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         oldChildren.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
 
         val savedState = oldStateKeeper.save()
@@ -200,13 +198,13 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_not_saving_state_WHEN_recreated_THEN_child_states_not_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by ACTIVE), saveNavState = { null })
+        val oldChildren by oldContext.children(initialState = stateOf(1 by ACTIVE), saveState = { null })
         oldChildren.getById(id = 1).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(initialNavState = listOf(1 by ACTIVE))
+        val newChildren by newContext.children(initialState = stateOf(1 by ACTIVE))
         val restoredState3 = newChildren.getById(id = 1).requireInstance().stateKeeper.consume<Config>(key = "key")
 
         assertNull(restoredState3)
@@ -216,13 +214,13 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_not_restoring_state_WHEN_recreated_THEN_child_states_not_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by ACTIVE), saveNavState = { null })
+        val oldChildren by oldContext.children(initialState = stateOf(1 by ACTIVE), saveState = { null })
         oldChildren.getById(id = 1).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(initialNavState = listOf(1 by ACTIVE), restoreNavState = { null })
+        val newChildren by newContext.children(initialState = stateOf(1 by ACTIVE), restoreState = { null })
         val restoredState3 = newChildren.getById(id = 1).requireInstance().stateKeeper.consume<Config>(key = "key")
 
         assertNull(restoredState3)
@@ -232,12 +230,12 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_recreated_and_child_restored_as_destroyed_WHEN_child_switched_to_inactive_THEN_state_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         oldChildren.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(restoreNavState = { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
+        val newChildren by newContext.children(restoreState = { stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
         val restoredState3 = newChildren.getById(id = 3).requireInstance().stateKeeper.consume<Config>(key = "key")
@@ -249,12 +247,12 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_recreated_and_child_restored_as_destroyed_WHEN_child_switched_to_active_THEN_state_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         oldChildren.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(restoreNavState = { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
+        val newChildren by newContext.children(restoreState = { stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE) }
         val restoredState3 = newChildren.getById(id = 3).requireInstance().stateKeeper.consume<Config>(key = "key")
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
index c4bf74e1..6f52ac11 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
@@ -3,8 +3,6 @@ package com.arkivanov.decompose.router.children
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
 import com.arkivanov.decompose.DefaultComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.Relay
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
 import com.arkivanov.decompose.value.Value
 import com.arkivanov.essenty.backhandler.BackDispatcher
@@ -18,10 +16,9 @@ import com.arkivanov.essenty.parcelable.consumeRequired
 import kotlin.test.BeforeTest
 import kotlin.test.assertContentEquals
 
-@OptIn(ExperimentalDecomposeApi::class)
 internal open class ChildrenTestBase {
 
-    private val navigation = TestNavigation()
+    private val navigation = SimpleNavigation<(TestNavState) -> TestNavState>()
     protected val lifecycle = LifecycleRegistry()
     protected val backDispatcher = BackDispatcher()
 
@@ -39,63 +36,59 @@ internal open class ChildrenTestBase {
     }
 
     protected fun ComponentContext.children(
-        initialNavState: List<SimpleChildNavState<Config>> = emptyList(),
-        saveNavState: (navState: List<SimpleChildNavState<Config>>) -> ParcelableContainer? = { navState ->
+        initialState: TestNavState = TestNavState(),
+        saveState: (state: TestNavState) -> ParcelableContainer? = { state ->
             ParcelableContainer(
                 SavedNavState(
-                    configurations = navState.map { it.configuration },
-                    statuses = navState.map { it.status },
+                    configurations = state.children.map { it.configuration },
+                    statuses = state.children.map { it.status },
                 )
             )
         },
-        restoreNavState: (container: ParcelableContainer) -> List<SimpleChildNavState<Config>>? = { container ->
+        restoreState: (container: ParcelableContainer) -> TestNavState? = { container ->
             val savedState = container.consumeRequired<SavedNavState>()
-            savedState.configurations.zip(savedState.statuses).map { (configuration, status) ->
-                SimpleChildNavState(
-                    configuration = configuration,
-                    status = status,
-                )
-            }
+            TestNavState(
+                children = savedState.configurations.zip(savedState.statuses).map { (configuration, status) ->
+                    SimpleChildNavState(
+                        configuration = configuration,
+                        status = status,
+                    )
+                },
+            )
         },
-        backTransformer: (navState: List<SimpleChildNavState<Config>>) -> (() -> List<SimpleChildNavState<Config>>)? = { null },
+        onStateChanged: (newState: TestNavState, oldState: TestNavState?) -> Unit = { _, _ -> },
+        onEventComplete: (
+            event: (TestNavState) -> TestNavState,
+            newState: TestNavState,
+            oldState: TestNavState,
+        ) -> Unit = { _, _, _ -> },
+        backTransformer: (state: TestNavState) -> (() -> TestNavState)? = { null },
         childFactory: (Config, ComponentContext) -> Component = ::Component,
     ): Value<List<Child<Config, Component>>> =
         children(
             source = navigation,
             key = "Key",
-            initialNavState = { TestNavState(children = initialNavState) },
-            saveNavState = { navState -> saveNavState(navState.children) },
-            restoreNavState = { container -> restoreNavState(container)?.let(::TestNavState) },
-            navTransformer = { navState, event -> event.transformer(navState) },
-            onEventComplete = { event, newNavState, oldNavState -> event.onComplete(newNavState, oldNavState) },
-            backTransformer = {
-                backTransformer(it.children)?.let { transformer ->
-                    { TestNavState(children = transformer()) }
-                }
-            },
+            initialState = { initialState },
+            saveState = saveState,
+            restoreState = restoreState,
+            navTransformer = { state, event -> event(state) },
+            onStateChanged = onStateChanged,
+            onEventComplete = onEventComplete,
+            backTransformer = backTransformer,
             stateMapper = { _, children -> children },
             childFactory = childFactory,
         )
 
     protected fun navigate(transformer: (List<SimpleChildNavState<Config>>) -> List<SimpleChildNavState<Config>>) {
-        navigate(transformer = transformer, onComplete = null)
-    }
-
-    protected fun navigate(
-        transformer: (List<SimpleChildNavState<Config>>) -> List<SimpleChildNavState<Config>>,
-        onComplete: ((newNavState: List<SimpleChildNavState<Config>>, oldNavState: List<SimpleChildNavState<Config>>) -> Unit)?,
-    ) {
-        navigation.navigate(
-            Event(
-                transformer = { TestNavState(children = transformer(it.children)) },
-                onComplete = { newNavState, oldNavState -> onComplete?.invoke(newNavState.children, oldNavState.children) },
-            )
-        )
+        navigation.navigate { it.copy(children = transformer(it.children)) }
     }
 
     protected infix fun Int.by(status: ChildNavState.Status): SimpleChildNavState<Config> =
         SimpleChildNavState(configuration = Config(id = this), status = status)
 
+    protected fun stateOf(vararg children: SimpleChildNavState<Config>): TestNavState =
+        TestNavState(children = children.asList())
+
     protected fun List<Child<Config, Component>>.assertChildren(vararg children: Pair<Int, Int?>) {
         assertContentEquals(
             children.toList(),
@@ -135,25 +128,4 @@ internal open class ChildrenTestBase {
         val configurations: List<Config>,
         val statuses: List<ChildNavState.Status>,
     ) : Parcelable
-
-    protected class Event(
-        val transformer: (TestNavState) -> TestNavState,
-        val onComplete: (newNavState: TestNavState, oldNavState: TestNavState) -> Unit,
-    )
-
-    protected class TestNavigation : NavigationSource<Event> {
-        private val relay = Relay<Event>()
-
-        override fun subscribe(observer: (Event) -> Unit) {
-            relay.subscribe(observer)
-        }
-
-        override fun unsubscribe(observer: (Event) -> Unit) {
-            relay.unsubscribe(observer)
-        }
-
-        fun navigate(event: Event) {
-            relay.accept(event)
-        }
-    }
 }