Repository: arkivanov/Decompose
Pull request #859 (2025-03-24 21:39:08)

Added backHandlerPriority argument to childContext extension function


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Enhanced the component context API to allow specification of a back navigation priority, giving you finer control over nested component behavior while maintaining backward compatibility.
  - Added new overloads for the `childContext` method to support additional parameters, including an integer.

- **Tests**
  - Introduced tests to verify that back navigation callbacks execute in the correct order based on their defined priorities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

coderabbitai[bot]: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->

> [!NOTE]
> Currently processing new changes in this PR. This may take a few minutes, please wait...
> 
> <details>
> <summary>📥 Commits</summary>
> 
> Reviewing files that changed from the base of the PR and between 0c09dd03075508098f83d980b29f34a7b25244b8 and 9bc2b7f4dd4d040458d83d8c3c6a2cba34464502.
> 
> </details>
> 
> <details>
> <summary>📒 Files selected for processing (5)</summary>
> 
> * `decompose/api/android/decompose.api` (1 hunks)
> * `decompose/api/decompose.klib.api` (1 hunks)
> * `decompose/api/jvm/decompose.api` (1 hunks)
> * `decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt` (1 hunks)
> * `decompose/src/commonTest/kotlin/com/arkivanov/decompose/ChildContextWithBackHandlerPriorityTest.kt` (1 hunks)
> 
> </details>
> 
> ```ascii
>  __________________
> < Zero bugs given. >
>  ------------------
>   \
>    \   \
>         \ /\
>         ( )
>       .( o ).
> ```

<!-- end of auto-generated comment: review in progress by coderabbit.ai -->
<!-- usage_tips_start -->

> [!TIP]
> <details>
> <summary>You can validate your CodeRabbit configuration file in your editor.</summary>
> 
> If your editor has YAML language server, you can enable auto-completion and validation by adding `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json` at the top of your CodeRabbit configuration file.
> 
> </details>

<!-- usage_tips_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=arkivanov/Decompose&utm_content=859):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Patch:
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index d584f3f7..e59e21d1 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -47,6 +47,8 @@ public abstract interface class com/arkivanov/decompose/ComponentContext : com/a
 
 public final class com/arkivanov/decompose/ComponentContextExtKt {
 	public static final fun childContext (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;)Lcom/arkivanov/decompose/GenericComponentContext;
+	public static final fun childContext (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;I)Lcom/arkivanov/decompose/GenericComponentContext;
+	public static synthetic fun childContext$default (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;IILjava/lang/Object;)Lcom/arkivanov/decompose/GenericComponentContext;
 	public static synthetic fun childContext$default (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;ILjava/lang/Object;)Lcom/arkivanov/decompose/GenericComponentContext;
 }
 
diff --git a/decompose/api/decompose.klib.api b/decompose/api/decompose.klib.api
index 1337ac98..054c29ad 100644
--- a/decompose/api/decompose.klib.api
+++ b/decompose/api/decompose.klib.api
@@ -495,6 +495,7 @@ final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/A
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.stack/childStack(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.stack/StackNavigation.Event<#B>>, kotlinx.serialization/KSerializer<#B>?, #B, kotlin/String = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.stack/ChildStack<#B, #C>> // com.arkivanov.decompose.router.stack/childStack|childStack@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.stack.StackNavigation.Event<0:1>>;kotlinx.serialization.KSerializer<0:1>?;0:1;kotlin.String;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>}[0]
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.stack/childStack(com.arkivanov.decompose.router.children/NavigationSource<com.arkivanov.decompose.router.stack/StackNavigation.Event<#B>>, kotlinx.serialization/KSerializer<#B>?, kotlin/Function0<kotlin.collections/List<#B>>, kotlin/String = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.stack/ChildStack<#B, #C>> // com.arkivanov.decompose.router.stack/childStack|childStack@0:0(com.arkivanov.decompose.router.children.NavigationSource<com.arkivanov.decompose.router.stack.StackNavigation.Event<0:1>>;kotlinx.serialization.KSerializer<0:1>?;kotlin.Function0<kotlin.collections.List<0:1>>;kotlin.String;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>;1§<kotlin.Any>;2§<kotlin.Any>}[0]
 final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>> (#A).com.arkivanov.decompose/childContext(kotlin/String, com.arkivanov.essenty.lifecycle/Lifecycle? = ...): #A // com.arkivanov.decompose/childContext|childContext@0:0(kotlin.String;com.arkivanov.essenty.lifecycle.Lifecycle?){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>}[0]
+final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>> (#A).com.arkivanov.decompose/childContext(kotlin/String, com.arkivanov.essenty.lifecycle/Lifecycle? = ..., kotlin/Int): #A // com.arkivanov.decompose/childContext|childContext@0:0(kotlin.String;com.arkivanov.essenty.lifecycle.Lifecycle?;kotlin.Int){0§<com.arkivanov.decompose.GenericComponentContext<0:0>>}[0]
 final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any, #F: kotlin/Any> com.arkivanov.decompose.router.panels/childPanelsWebNavigation(com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #C, #E>, com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, #E, #F>>, kotlin/Triple<kotlinx.serialization/KSerializer<#A>, kotlinx.serialization/KSerializer<#C>, kotlinx.serialization/KSerializer<#E>>, kotlin/Function1<com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, #E, #F>, kotlin/String?> = ..., kotlin/Function1<com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, #E, #F>, kotlin.collections/Map<kotlin/String, kotlin/String>?> = ..., kotlin/Function0<kotlin/Boolean> = ..., kotlin/Function1<com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, #E, #F>, com.arkivanov.decompose.router.webhistory/WebNavigationOwner?> = ...): com.arkivanov.decompose.router.webhistory/WebNavigation<*> // com.arkivanov.decompose.router.panels/childPanelsWebNavigation|childPanelsWebNavigation(com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:2,0:4>;com.arkivanov.decompose.value.Value<com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,0:4,0:5>>;kotlin.Triple<kotlinx.serialization.KSerializer<0:0>,kotlinx.serialization.KSerializer<0:2>,kotlinx.serialization.KSerializer<0:4>>;kotlin.Function1<com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,0:4,0:5>,kotlin.String?>;kotlin.Function1<com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,0:4,0:5>,kotlin.collections.Map<kotlin.String,kotlin.String>?>;kotlin.Function0<kotlin.Boolean>;kotlin.Function1<com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,0:4,0:5>,com.arkivanov.decompose.router.webhistory.WebNavigationOwner?>){0§<kotlin.Any>;1§<kotlin.Any>;2§<kotlin.Any>;3§<kotlin.Any>;4§<kotlin.Any>;5§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any> com.arkivanov.decompose.router.panels/childPanelsWebNavigation(com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #C, kotlin/Nothing>, com.arkivanov.decompose.value/Value<com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, kotlin/Nothing, kotlin/Nothing>>, kotlin/Pair<kotlinx.serialization/KSerializer<#A>, kotlinx.serialization/KSerializer<#C>>, kotlin/Function1<com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, kotlin/Nothing, kotlin/Nothing>, kotlin/String?> = ..., kotlin/Function1<com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, kotlin/Nothing, kotlin/Nothing>, kotlin.collections/Map<kotlin/String, kotlin/String>?> = ..., kotlin/Function0<kotlin/Boolean> = ..., kotlin/Function1<com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, kotlin/Nothing, kotlin/Nothing>, com.arkivanov.decompose.router.webhistory/WebNavigationOwner?> = ...): com.arkivanov.decompose.router.webhistory/WebNavigation<*> // com.arkivanov.decompose.router.panels/childPanelsWebNavigation|childPanelsWebNavigation(com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:2,kotlin.Nothing>;com.arkivanov.decompose.value.Value<com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,kotlin.Nothing,kotlin.Nothing>>;kotlin.Pair<kotlinx.serialization.KSerializer<0:0>,kotlinx.serialization.KSerializer<0:2>>;kotlin.Function1<com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,kotlin.Nothing,kotlin.Nothing>,kotlin.String?>;kotlin.Function1<com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,kotlin.Nothing,kotlin.Nothing>,kotlin.collections.Map<kotlin.String,kotlin.String>?>;kotlin.Function0<kotlin.Boolean>;kotlin.Function1<com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,kotlin.Nothing,kotlin.Nothing>,com.arkivanov.decompose.router.webhistory.WebNavigationOwner?>){0§<kotlin.Any>;1§<kotlin.Any>;2§<kotlin.Any>;3§<kotlin.Any>}[0]
 final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/activateDetails(#B, kotlin/Function2<com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/activateDetails|activateDetails@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(0:1;kotlin.Function2<com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§<kotlin.Any>;1§<kotlin.Any>;2§<kotlin.Any>}[0]
diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api
index f2059c39..4e9a33fc 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -47,6 +47,8 @@ public abstract interface class com/arkivanov/decompose/ComponentContext : com/a
 
 public final class com/arkivanov/decompose/ComponentContextExtKt {
 	public static final fun childContext (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;)Lcom/arkivanov/decompose/GenericComponentContext;
+	public static final fun childContext (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;I)Lcom/arkivanov/decompose/GenericComponentContext;
+	public static synthetic fun childContext$default (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;IILjava/lang/Object;)Lcom/arkivanov/decompose/GenericComponentContext;
 	public static synthetic fun childContext$default (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;ILjava/lang/Object;)Lcom/arkivanov/decompose/GenericComponentContext;
 }
 
diff --git a/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DeeplinkUtils.kt b/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DeeplinkUtils.kt
index 1635e8c7..f3a98b22 100644
--- a/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DeeplinkUtils.kt
+++ b/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DeeplinkUtils.kt
@@ -51,15 +51,13 @@ import androidx.savedstate.SavedStateRegistryOwner
 fun <A, T : Any> A.handleDeepLink(
     block: (Uri?) -> T,
 ): T? where A : Activity, A : SavedStateRegistryOwner {
-    val intentData: Uri? = intent.data
-
-    if ((intentData != null) && restartIfNeeded()) {
+    if (restartIfNeeded()) {
         return null
     }
 
     val savedState: Bundle? = savedStateRegistry.consumeRestoredStateForKey(key = KEY_SAVED_DEEP_LINK_STATE)
     val isDeepLinkHandled = savedState?.getBoolean(KEY_DEEP_LINK_HANDLED) ?: false
-    val deepLink = intentData?.takeUnless { isDeepLinkHandled }
+    val deepLink = intent.data.takeUnless { isDeepLinkHandled }
 
     savedStateRegistry.registerSavedStateProvider(key = KEY_SAVED_DEEP_LINK_STATE) {
         bundleOf(KEY_DEEP_LINK_HANDLED to (isDeepLinkHandled || (deepLink != null)))
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt
index 0cd75d8f..54704954 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt
@@ -4,11 +4,13 @@ import com.arkivanov.decompose.backhandler.child
 import com.arkivanov.decompose.instancekeeper.child
 import com.arkivanov.decompose.lifecycle.MergedLifecycle
 import com.arkivanov.decompose.statekeeper.child
+import com.arkivanov.essenty.backhandler.BackCallback
 import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.doOnDestroy
 
 /**
- * Creates a new instance of child component context of type [Ctx] and attaches it to the parent (`this`) component context.
+ * Creates a new instance of child [ComponentContext] of type [Ctx] and attaches it to the parent (`this`) component context.
+ * Can be used to create fixed child components (i.e. without navigation) living as long as the parent (hosting) component.
  *
  * @param key A key of the child component context, must be unique within the parent context.
  * @param lifecycle An optional [Lifecycle] of the child component context to be merged with the parent [Lifecycle],
@@ -17,13 +19,41 @@ import com.arkivanov.essenty.lifecycle.doOnDestroy
  * - The resulting [Lifecycle] of the child component will honour both the parent (`this`) [Lifecycle] and the supplied one.
  * - The supplied [Lifecycle] must never be destroyed manually.
  */
-fun <Ctx : GenericComponentContext<Ctx>> Ctx.childContext(key: String, lifecycle: Lifecycle? = null): Ctx {
+fun <Ctx : GenericComponentContext<Ctx>> Ctx.childContext(key: String, lifecycle: Lifecycle? = null): Ctx =
+    childContext(
+        key = key,
+        lifecycle = lifecycle,
+        backHandlerPriority = BackCallback.PRIORITY_DEFAULT,
+    )
+
+/**
+ * Creates a new instance of child [ComponentContext] of type [Ctx] and attaches it to the parent (`this`) component context.
+ * Can be used to create fixed child components (i.e. without navigation) living as long as the parent (hosting) component.
+ *
+ * @param key A key of the child component context, must be unique within the parent context.
+ * @param lifecycle An optional [Lifecycle] of the child component context to be merged with the parent [Lifecycle],
+ * can be used for manual control (see [LifecycleRegistry][com.arkivanov.essenty.lifecycle.LifecycleRegistry]).
+ * The following conditions apply:
+ * - The resulting [Lifecycle] of the child component will honour both the parent (`this`) [Lifecycle] and the supplied one.
+ * - The supplied [Lifecycle] must never be destroyed manually.
+ * @param backHandlerPriority a priority of the `BackHandler` used by the returned [ComponentContext] in the scope of the
+ * parent (`this`) [ComponentContext]. By default, back handlers of the child component contexts and normal `BackCallbacks`
+ * registered in the parent (hosting) component context's `BackHandler` handle the back button in the reverse order.
+ * E.g. with two child component contexts, the second one handles the back button before the first one.
+ * The [backHandlerPriority] parameter allows to explicitly control the order.
+ */
+@ExperimentalDecomposeApi
+fun <Ctx : GenericComponentContext<Ctx>> Ctx.childContext(
+    key: String,
+    lifecycle: Lifecycle? = null,
+    backHandlerPriority: Int,
+): Ctx {
     lifecycle?.doOnDestroy { error("The lifecycle of a child ComponentContext must never be destroyed manually.") }
 
     return componentContextFactory(
         lifecycle = if (lifecycle == null) this.lifecycle else MergedLifecycle(this.lifecycle, lifecycle),
         stateKeeper = stateKeeper.child(key, lifecycle),
         instanceKeeper = instanceKeeper.child(key, lifecycle),
-        backHandler = backHandler.child(lifecycle),
+        backHandler = backHandler.child(lifecycle, backHandlerPriority),
     )
 }


Test patch:
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/ChildContextWithBackHandlerPriorityTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/ChildContextWithBackHandlerPriorityTest.kt
new file mode 100644
index 00000000..6c1a4f7e
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/ChildContextWithBackHandlerPriorityTest.kt
@@ -0,0 +1,88 @@
+package com.arkivanov.decompose
+
+import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
+import com.arkivanov.essenty.backhandler.BackCallback
+import com.arkivanov.essenty.backhandler.BackDispatcher
+import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
+import com.arkivanov.essenty.lifecycle.LifecycleRegistry
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+@Suppress("TestFunctionName")
+class ChildContextWithBackHandlerPriorityTest {
+
+    @Test
+    fun GIVEN_backHandlerPriority_specified_WHEN_back_THEN_order_according_to_priority() {
+        val backDispatcher = BackDispatcher()
+        val context = TestContext(backDispatcher)
+        val childContext1 = context.childContext(key = "context1", backHandlerPriority = 3)
+        val childContext2 = context.childContext(key = "context2", backHandlerPriority = 4)
+        val childContext3 = context.childContext(key = "context3", backHandlerPriority = 2)
+        val events = ArrayList<String>()
+        val backCallback01 = BackCallback(priority = 1) { events += "01" }
+        val backCallback02 = BackCallback(priority = 5) { events += "02" }
+        val backCallback1 = BackCallback { events += "1" }
+        val backCallback2 = BackCallback { events += "2" }
+        val backCallback3 = BackCallback { events += "3" }
+        context.backHandler.register(backCallback01)
+        context.backHandler.register(backCallback02)
+        childContext1.backHandler.register(backCallback1)
+        childContext2.backHandler.register(backCallback2)
+        childContext3.backHandler.register(backCallback3)
+
+        backDispatcher.back()
+        backCallback02.isEnabled = false
+        backDispatcher.back()
+        backCallback2.isEnabled = false
+        backDispatcher.back()
+        backCallback1.isEnabled = false
+        backDispatcher.back()
+        backCallback3.isEnabled = false
+        backDispatcher.back()
+
+        assertEquals(listOf("02", "2", "1", "3", "01"), events)
+    }
+
+    @Test
+    fun GIVEN_backHandlerPriority_not_specified_WHEN_back_THEN_reverse_order() {
+        val backDispatcher = BackDispatcher()
+        val context = TestContext(backDispatcher)
+        val childContext1 = context.childContext(key = "context1")
+        val childContext2 = context.childContext(key = "context2")
+        val childContext3 = context.childContext(key = "context3")
+        val events = ArrayList<String>()
+        val backCallback01 = BackCallback { events += "01" }
+        val backCallback02 = BackCallback { events += "02" }
+        val backCallback1 = BackCallback { events += "1" }
+        val backCallback2 = BackCallback { events += "2" }
+        val backCallback3 = BackCallback { events += "3" }
+        context.backHandler.register(backCallback01)
+        context.backHandler.register(backCallback02)
+        childContext1.backHandler.register(backCallback1)
+        childContext2.backHandler.register(backCallback2)
+        childContext3.backHandler.register(backCallback3)
+
+        backDispatcher.back()
+        backCallback02.isEnabled = false
+        backDispatcher.back()
+        backCallback01.isEnabled = false
+        backDispatcher.back()
+        backCallback3.isEnabled = false
+        backDispatcher.back()
+        backCallback2.isEnabled = false
+        backDispatcher.back()
+        backCallback1.isEnabled = false
+        backDispatcher.back()
+
+        assertEquals(listOf("02", "01", "3", "2", "1"), events)
+    }
+
+    private class TestContext(
+        override val backHandler: BackDispatcher = BackDispatcher()
+    ) : ComponentContext {
+        override val lifecycle: LifecycleRegistry = LifecycleRegistry()
+        override val stateKeeper: TestStateKeeperDispatcher = TestStateKeeperDispatcher()
+        override val instanceKeeper: InstanceKeeperDispatcher = InstanceKeeperDispatcher()
+        override val componentContextFactory: ComponentContextFactory<ComponentContext> = ComponentContextFactory(::DefaultComponentContext)
+    }
+}