Skip to content

Commit d32e837

Browse files
authored
Merge pull request #859 from arkivanov/backHandlerPriority
Added backHandlerPriority argument to childContext extension function
2 parents 0f9bc3a + 9290fb3 commit d32e837

File tree

5 files changed

+126
-3
lines changed

5 files changed

+126
-3
lines changed

decompose/api/android/decompose.api

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public abstract interface class com/arkivanov/decompose/ComponentContext : com/a
4747

4848
public final class com/arkivanov/decompose/ComponentContextExtKt {
4949
public static final fun childContext (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;)Lcom/arkivanov/decompose/GenericComponentContext;
50+
public static final fun childContext (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;I)Lcom/arkivanov/decompose/GenericComponentContext;
51+
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;
5052
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;
5153
}
5254

decompose/api/decompose.klib.api

+1
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/A
495495
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]
496496
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]
497497
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]
498+
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]
498499
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]
499500
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]
500501
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]

decompose/api/jvm/decompose.api

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public abstract interface class com/arkivanov/decompose/ComponentContext : com/a
4747

4848
public final class com/arkivanov/decompose/ComponentContextExtKt {
4949
public static final fun childContext (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;)Lcom/arkivanov/decompose/GenericComponentContext;
50+
public static final fun childContext (Lcom/arkivanov/decompose/GenericComponentContext;Ljava/lang/String;Lcom/arkivanov/essenty/lifecycle/Lifecycle;I)Lcom/arkivanov/decompose/GenericComponentContext;
51+
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;
5052
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;
5153
}
5254

decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt

+33-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import com.arkivanov.decompose.backhandler.child
44
import com.arkivanov.decompose.instancekeeper.child
55
import com.arkivanov.decompose.lifecycle.MergedLifecycle
66
import com.arkivanov.decompose.statekeeper.child
7+
import com.arkivanov.essenty.backhandler.BackCallback
78
import com.arkivanov.essenty.lifecycle.Lifecycle
89
import com.arkivanov.essenty.lifecycle.doOnDestroy
910

1011
/**
11-
* Creates a new instance of child component context of type [Ctx] and attaches it to the parent (`this`) component context.
12+
* Creates a new instance of child [ComponentContext] of type [Ctx] and attaches it to the parent (`this`) component context.
13+
* Can be used to create fixed child components (i.e. without navigation) living as long as the parent (hosting) component.
1214
*
1315
* @param key A key of the child component context, must be unique within the parent context.
1416
* @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
1719
* - The resulting [Lifecycle] of the child component will honour both the parent (`this`) [Lifecycle] and the supplied one.
1820
* - The supplied [Lifecycle] must never be destroyed manually.
1921
*/
20-
fun <Ctx : GenericComponentContext<Ctx>> Ctx.childContext(key: String, lifecycle: Lifecycle? = null): Ctx {
22+
fun <Ctx : GenericComponentContext<Ctx>> Ctx.childContext(key: String, lifecycle: Lifecycle? = null): Ctx =
23+
childContext(
24+
key = key,
25+
lifecycle = lifecycle,
26+
backHandlerPriority = BackCallback.PRIORITY_DEFAULT,
27+
)
28+
29+
/**
30+
* Creates a new instance of child [ComponentContext] of type [Ctx] and attaches it to the parent (`this`) component context.
31+
* Can be used to create fixed child components (i.e. without navigation) living as long as the parent (hosting) component.
32+
*
33+
* @param key A key of the child component context, must be unique within the parent context.
34+
* @param lifecycle An optional [Lifecycle] of the child component context to be merged with the parent [Lifecycle],
35+
* can be used for manual control (see [LifecycleRegistry][com.arkivanov.essenty.lifecycle.LifecycleRegistry]).
36+
* The following conditions apply:
37+
* - The resulting [Lifecycle] of the child component will honour both the parent (`this`) [Lifecycle] and the supplied one.
38+
* - The supplied [Lifecycle] must never be destroyed manually.
39+
* @param backHandlerPriority a priority of the `BackHandler` used by the returned [ComponentContext] in the scope of the
40+
* parent (`this`) [ComponentContext]. By default, back handlers of the child component contexts and normal `BackCallbacks`
41+
* registered in the parent (hosting) component context's `BackHandler` handle the back button in the reverse order.
42+
* E.g. with two child component contexts, the second one handles the back button before the first one.
43+
* The [backHandlerPriority] parameter allows to explicitly control the order.
44+
*/
45+
@ExperimentalDecomposeApi
46+
fun <Ctx : GenericComponentContext<Ctx>> Ctx.childContext(
47+
key: String,
48+
lifecycle: Lifecycle? = null,
49+
backHandlerPriority: Int,
50+
): Ctx {
2151
lifecycle?.doOnDestroy { error("The lifecycle of a child ComponentContext must never be destroyed manually.") }
2252

2353
return componentContextFactory(
2454
lifecycle = if (lifecycle == null) this.lifecycle else MergedLifecycle(this.lifecycle, lifecycle),
2555
stateKeeper = stateKeeper.child(key, lifecycle),
2656
instanceKeeper = instanceKeeper.child(key, lifecycle),
27-
backHandler = backHandler.child(lifecycle),
57+
backHandler = backHandler.child(lifecycle, backHandlerPriority),
2858
)
2959
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.arkivanov.decompose
2+
3+
import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
4+
import com.arkivanov.essenty.backhandler.BackCallback
5+
import com.arkivanov.essenty.backhandler.BackDispatcher
6+
import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
7+
import com.arkivanov.essenty.lifecycle.LifecycleRegistry
8+
import kotlin.test.Test
9+
import kotlin.test.assertEquals
10+
11+
@Suppress("TestFunctionName")
12+
class ChildContextWithBackHandlerPriorityTest {
13+
14+
@Test
15+
fun GIVEN_backHandlerPriority_specified_WHEN_back_THEN_order_according_to_priority() {
16+
val backDispatcher = BackDispatcher()
17+
val context = TestContext(backDispatcher)
18+
val childContext1 = context.childContext(key = "context1", backHandlerPriority = 3)
19+
val childContext2 = context.childContext(key = "context2", backHandlerPriority = 4)
20+
val childContext3 = context.childContext(key = "context3", backHandlerPriority = 2)
21+
val events = ArrayList<String>()
22+
val backCallback01 = BackCallback(priority = 1) { events += "01" }
23+
val backCallback02 = BackCallback(priority = 5) { events += "02" }
24+
val backCallback1 = BackCallback { events += "1" }
25+
val backCallback2 = BackCallback { events += "2" }
26+
val backCallback3 = BackCallback { events += "3" }
27+
context.backHandler.register(backCallback01)
28+
context.backHandler.register(backCallback02)
29+
childContext1.backHandler.register(backCallback1)
30+
childContext2.backHandler.register(backCallback2)
31+
childContext3.backHandler.register(backCallback3)
32+
33+
backDispatcher.back()
34+
backCallback02.isEnabled = false
35+
backDispatcher.back()
36+
backCallback2.isEnabled = false
37+
backDispatcher.back()
38+
backCallback1.isEnabled = false
39+
backDispatcher.back()
40+
backCallback3.isEnabled = false
41+
backDispatcher.back()
42+
43+
assertEquals(listOf("02", "2", "1", "3", "01"), events)
44+
}
45+
46+
@Test
47+
fun GIVEN_backHandlerPriority_not_specified_WHEN_back_THEN_reverse_order() {
48+
val backDispatcher = BackDispatcher()
49+
val context = TestContext(backDispatcher)
50+
val childContext1 = context.childContext(key = "context1")
51+
val childContext2 = context.childContext(key = "context2")
52+
val childContext3 = context.childContext(key = "context3")
53+
val events = ArrayList<String>()
54+
val backCallback01 = BackCallback { events += "01" }
55+
val backCallback02 = BackCallback { events += "02" }
56+
val backCallback1 = BackCallback { events += "1" }
57+
val backCallback2 = BackCallback { events += "2" }
58+
val backCallback3 = BackCallback { events += "3" }
59+
context.backHandler.register(backCallback01)
60+
context.backHandler.register(backCallback02)
61+
childContext1.backHandler.register(backCallback1)
62+
childContext2.backHandler.register(backCallback2)
63+
childContext3.backHandler.register(backCallback3)
64+
65+
backDispatcher.back()
66+
backCallback02.isEnabled = false
67+
backDispatcher.back()
68+
backCallback01.isEnabled = false
69+
backDispatcher.back()
70+
backCallback3.isEnabled = false
71+
backDispatcher.back()
72+
backCallback2.isEnabled = false
73+
backDispatcher.back()
74+
backCallback1.isEnabled = false
75+
backDispatcher.back()
76+
77+
assertEquals(listOf("02", "01", "3", "2", "1"), events)
78+
}
79+
80+
private class TestContext(
81+
override val backHandler: BackDispatcher = BackDispatcher()
82+
) : ComponentContext {
83+
override val lifecycle: LifecycleRegistry = LifecycleRegistry()
84+
override val stateKeeper: TestStateKeeperDispatcher = TestStateKeeperDispatcher()
85+
override val instanceKeeper: InstanceKeeperDispatcher = InstanceKeeperDispatcher()
86+
override val componentContextFactory: ComponentContextFactory<ComponentContext> = ComponentContextFactory(::DefaultComponentContext)
87+
}
88+
}

0 commit comments

Comments
 (0)