From 4e948e4bc3a9bd0ee364a6f8ddf5d982394e53c8 Mon Sep 17 00:00:00 2001 From: Igor Demin Date: Mon, 19 Feb 2024 20:30:20 +0100 Subject: [PATCH] Resources. Don't return a cached value when pass new args. The issue was because we cache the value in the current composition, and the next composition returns the cached value. There weren't an issue if we just call one `stringResource` after another because those are different compositions. Fixes https://github.com/JetBrains/compose-multiplatform/issues/4325 --- .../resources/ResourceState.blocking.kt | 35 +++++++++++++++++- .../compose/resources/ResourceState.kt | 29 ++++++++++++++- .../compose/resources/StringResources.kt | 2 +- .../compose/resources/ComposeResourceTest.kt | 23 ++++++++++++ .../compose/resources/FontResources.skiko.kt | 2 +- .../compose/resources/ResourceState.web.kt | 37 +++++++++++++++++-- 6 files changed, 120 insertions(+), 8 deletions(-) diff --git a/components/resources/library/src/blockingMain/kotlin/org/jetbrains/compose/resources/ResourceState.blocking.kt b/components/resources/library/src/blockingMain/kotlin/org/jetbrains/compose/resources/ResourceState.blocking.kt index b172f792c93..6621a723610 100644 --- a/components/resources/library/src/blockingMain/kotlin/org/jetbrains/compose/resources/ResourceState.blocking.kt +++ b/components/resources/library/src/blockingMain/kotlin/org/jetbrains/compose/resources/ResourceState.blocking.kt @@ -5,12 +5,43 @@ import kotlinx.coroutines.runBlocking @Composable internal actual fun rememberResourceState( - key: Any, + key1: Any, getDefault: () -> T, block: suspend (ResourceEnvironment) -> T ): State { val environment = LocalComposeEnvironment.current.rememberEnvironment() - return remember(key, environment) { + return remember(key1, environment) { + mutableStateOf( + runBlocking { block(environment) } + ) + } +} + +@Composable +internal actual fun rememberResourceState( + key1: Any, + key2: Any, + getDefault: () -> T, + block: suspend (ResourceEnvironment) -> T +): State { + val environment = LocalComposeEnvironment.current.rememberEnvironment() + return remember(key1, key2, environment) { + mutableStateOf( + runBlocking { block(environment) } + ) + } +} + +@Composable +internal actual fun rememberResourceState( + key1: Any, + key2: Any, + key3: Any, + getDefault: () -> T, + block: suspend (ResourceEnvironment) -> T +): State { + val environment = LocalComposeEnvironment.current.rememberEnvironment() + return remember(key1, key2, key3, environment) { mutableStateOf( runBlocking { block(environment) } ) diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceState.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceState.kt index f09ba40827c..e8e38d5e63c 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceState.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceState.kt @@ -10,7 +10,34 @@ import androidx.compose.runtime.State */ @Composable internal expect fun rememberResourceState( - key: Any, + key1: Any, + getDefault: () -> T, + block: suspend (ResourceEnvironment) -> T +): State + +/** + * This is a platform-specific function that calculates and remembers a state. + * For all platforms except a JS it is a blocking function. + * On the JS platform it loads the state asynchronously and uses `getDefault` as an initial state value. + */ +@Composable +internal expect fun rememberResourceState( + key1: Any, + key2: Any, + getDefault: () -> T, + block: suspend (ResourceEnvironment) -> T +): State + +/** + * This is a platform-specific function that calculates and remembers a state. + * For all platforms except a JS it is a blocking function. + * On the JS platform it loads the state asynchronously and uses `getDefault` as an initial state value. + */ +@Composable +internal expect fun rememberResourceState( + key1: Any, + key2: Any, + key3: Any, getDefault: () -> T, block: suspend (ResourceEnvironment) -> T ): State \ No newline at end of file diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt index da3d53eb6c6..77913ab4957 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt @@ -123,7 +123,7 @@ private suspend fun loadString( fun stringResource(resource: StringResource, vararg formatArgs: Any): String { val resourceReader = LocalResourceReader.current val args = formatArgs.map { it.toString() } - val str by rememberResourceState(resource, { "" }) { env -> + val str by rememberResourceState(resource, args, { "" }) { env -> loadString(resource, args, resourceReader, env) } return str diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt index 3160db93b20..61949ee8425 100644 --- a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt @@ -111,6 +111,29 @@ class ComposeResourceTest { assertEquals(listOf("item 1", "item 2", "item 3"), str_arr) } + // https://github.com/JetBrains/compose-multiplatform/issues/4325 + @Test + fun testReadStringFromDifferentArgs() = runComposeUiTest { + var arg by mutableStateOf(42) + var str1 = "" + var str2 = "" + setContent { + CompositionLocalProvider(LocalComposeEnvironment provides TestComposeEnvironment) { + str1 = stringResource(TestStringResource("str_template"), "test1", arg) + str2 = stringResource(TestStringResource("str_template"), "test2", arg) + } + } + + waitForIdle() + assertEquals("Hello, test1! You have 42 new messages.", str1) + assertEquals("Hello, test2! You have 42 new messages.", str2) + + arg = 31415 + waitForIdle() + assertEquals("Hello, test1! You have 31415 new messages.", str1) + assertEquals("Hello, test2! You have 31415 new messages.", str2) + } + @Test fun testLoadStringResource() = runTest { assertEquals("Compose Resources App", getString(TestStringResource("app_name"))) diff --git a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt index 0bd899be072..c254829c816 100644 --- a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt +++ b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt @@ -33,7 +33,7 @@ private val defaultEmptyFont by lazy { Font("org.jetbrains.compose.emptyFont", B @Composable actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { val resourceReader = LocalResourceReader.current - val fontFile by rememberResourceState(resource, { defaultEmptyFont }) { env -> + val fontFile by rememberResourceState(resource, weight, style, { defaultEmptyFont }) { env -> val path = resource.getPathByEnvironment(env) val fontBytes = resourceReader.read(path) Font(path, fontBytes, weight, style) diff --git a/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt b/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt index ca6b0fd127d..8317c96d2a8 100644 --- a/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt +++ b/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt @@ -8,13 +8,44 @@ import androidx.compose.runtime.remember @Composable internal actual fun rememberResourceState( - key: Any, + key1: Any, getDefault: () -> T, block: suspend (ResourceEnvironment) -> T ): State { val environment = LocalComposeEnvironment.current.rememberEnvironment() - val state = remember(key) { mutableStateOf(getDefault()) } - LaunchedEffect(key) { + val state = remember(key1) { mutableStateOf(getDefault()) } + LaunchedEffect(key1) { + state.value = block(environment) + } + return state +} + +@Composable +internal actual fun rememberResourceState( + key1: Any, + key2: Any, + getDefault: () -> T, + block: suspend (ResourceEnvironment) -> T +): State { + val environment = LocalComposeEnvironment.current.rememberEnvironment() + val state = remember(key1, key2) { mutableStateOf(getDefault()) } + LaunchedEffect(key1, key2) { + state.value = block(environment) + } + return state +} + +@Composable +internal actual fun rememberResourceState( + key1: Any, + key2: Any, + key3: Any, + getDefault: () -> T, + block: suspend (ResourceEnvironment) -> T +): State { + val environment = LocalComposeEnvironment.current.rememberEnvironment() + val state = remember(key1, key2, key3) { mutableStateOf(getDefault()) } + LaunchedEffect(key1, key2, key3) { state.value = block(environment) } return state