From 872925b962c0cd1f6b16b663ea61bc8fb3bdd789 Mon Sep 17 00:00:00 2001 From: Arkadii Ivanov Date: Mon, 8 Jul 2024 14:53:40 +0100 Subject: [PATCH] Use configuration class names in key hash strings --- .../kotlin/com/arkivanov/decompose/Utils.kt | 4 +++ .../arkivanov/decompose/KeyHashStringTest.kt | 25 +++++++++++++++++++ .../android/stack/StackRouterView.kt | 6 ++--- .../extensions/compose/pages/Pages.kt | 7 +++--- .../extensions/compose/stack/Children.kt | 9 +++---- 5 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 decompose/src/commonTest/kotlin/com/arkivanov/decompose/KeyHashStringTest.kt diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt index bf6ec7e93..187c8a25a 100644 --- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt @@ -7,6 +7,10 @@ import kotlin.reflect.KClass fun Any.hashString(): String = "${this::class.uniqueName ?: this::class.simpleName}_${hashCode().toString(radix = 36)}" +@InternalDecomposeApi +fun Child<*, *>.keyHashString(): String = + "${configuration::class.uniqueName ?: configuration::class.simpleName}_${key.hashCode().toString(radix = 36)}" + internal expect val KClass<*>.uniqueName: String? internal val Lifecycle.isDestroyed: Boolean get() = state == Lifecycle.State.DESTROYED diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/KeyHashStringTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/KeyHashStringTest.kt new file mode 100644 index 000000000..8a27c4153 --- /dev/null +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/KeyHashStringTest.kt @@ -0,0 +1,25 @@ +package com.arkivanov.decompose + +import kotlin.test.Test +import kotlin.test.assertEquals + +class KeyHashStringTest { + + @Test + fun keyed_keyHashString_returns_distinct_values() { + val configs = listOf(Config.A(id = 1), Config.B(id = 1), Config.A(id = 1)) + + val keyStrings = + configs + .keyed { it } + .map { (key, config) -> Child.Destroyed(configuration = config, key = key) } + .map { it.keyHashString() } + + assertEquals(configs.size, keyStrings.distinct().size) + } + + private sealed interface Config { + data class A(val id: Int) : Config + data class B(val id: Int) : Config + } +} diff --git a/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt b/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt index 01bdc1145..e10493540 100644 --- a/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt +++ b/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt @@ -16,7 +16,7 @@ import com.arkivanov.decompose.extensions.android.DefaultViewContext import com.arkivanov.decompose.extensions.android.R import com.arkivanov.decompose.extensions.android.ViewContext import com.arkivanov.decompose.extensions.android.forEachChild -import com.arkivanov.decompose.hashString +import com.arkivanov.decompose.keyHashString import com.arkivanov.decompose.lifecycle.MergedLifecycle import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.value.Value @@ -99,7 +99,7 @@ class StackRouterView @JvmOverloads constructor( private fun onStackChanged( stack: ChildStack, lifecycle: Lifecycle, - replaceChildView: ViewContext.(parent: ViewGroup, newStack: ChildStack, oldStack: ChildStack?) -> Unit, + replaceChildView: ViewContext.(parent: ViewGroup, newStack: ChildStack, oldStack: ChildStack?) -> Unit, ) { val activeChild = stack.active @@ -126,7 +126,7 @@ class StackRouterView @JvmOverloads constructor( val newChildView = findNewChildView() - val activeChildKey = activeChild.key.hashString() + val activeChildKey = activeChild.keyHashString() newChildView.key = activeChildKey diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt index ad26a0b02..6127d6761 100644 --- a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt +++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/Pages.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -17,7 +16,7 @@ import com.arkivanov.decompose.Child import com.arkivanov.decompose.ExperimentalDecomposeApi import com.arkivanov.decompose.Ref import com.arkivanov.decompose.extensions.compose.subscribeAsState -import com.arkivanov.decompose.hashString +import com.arkivanov.decompose.keyHashString import com.arkivanov.decompose.router.pages.ChildPages import com.arkivanov.decompose.value.Value @@ -33,7 +32,7 @@ fun Pages( modifier: Modifier = Modifier, scrollAnimation: PagesScrollAnimation = PagesScrollAnimation.Disabled, pager: Pager = defaultHorizontalPager(), - key: (Child) -> Any = { it.key.hashString() }, + key: (Child) -> Any = Child<*, *>::keyHashString, pageContent: @Composable PagerScope.(index: Int, page: T) -> Unit, ) { val state by pages.subscribeAsState() @@ -61,7 +60,7 @@ fun Pages( modifier: Modifier = Modifier, scrollAnimation: PagesScrollAnimation = PagesScrollAnimation.Disabled, pager: Pager = defaultHorizontalPager(), - key: (Child) -> Any = { it.key.hashString() }, + key: (Child) -> Any = Child<*, *>::keyHashString, pageContent: @Composable PagerScope.(index: Int, page: T) -> Unit, ) { val selectedIndex = pages.selectedIndex diff --git a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt index d5d8015c3..067e16932 100644 --- a/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt +++ b/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/Children.kt @@ -7,16 +7,14 @@ import androidx.compose.runtime.saveable.SaveableStateHolder import androidx.compose.runtime.saveable.rememberSaveableStateHolder import androidx.compose.ui.Modifier import com.arkivanov.decompose.Child -import com.arkivanov.decompose.ExperimentalDecomposeApi import com.arkivanov.decompose.extensions.compose.stack.animation.LocalStackAnimationProvider import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation import com.arkivanov.decompose.extensions.compose.stack.animation.emptyStackAnimation import com.arkivanov.decompose.extensions.compose.subscribeAsState -import com.arkivanov.decompose.hashString +import com.arkivanov.decompose.keyHashString import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.value.Value -@OptIn(ExperimentalDecomposeApi::class) @Composable fun Children( stack: ChildStack, @@ -32,7 +30,7 @@ fun Children( val anim = animation ?: remember(animationProvider, animationProvider::provide) ?: emptyStackAnimation() anim(stack = stack, modifier = modifier) { child -> - holder.SaveableStateProvider(child.key.hashString()) { + holder.SaveableStateProvider(child.keyHashString()) { content(child) } } @@ -55,9 +53,8 @@ fun Children( ) } -@OptIn(ExperimentalDecomposeApi::class) private fun ChildStack<*, *>.getKeys(): Set = - items.mapTo(HashSet()) { it.key.hashString() } + items.mapTo(HashSet(), Child<*, *>::keyHashString) @Composable private fun SaveableStateHolder.retainStates(currentKeys: Set) {