Browse Source

Resources. Don't return a cached value when pass new args (#4333)

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
pull/4340/head
Igor Demin 9 months ago committed by GitHub
parent
commit
e14bf1c072
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 35
      components/resources/library/src/blockingMain/kotlin/org/jetbrains/compose/resources/ResourceState.blocking.kt
  2. 29
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceState.kt
  3. 2
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt
  4. 23
      components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.kt
  5. 2
      components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt
  6. 37
      components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt

35
components/resources/library/src/blockingMain/kotlin/org/jetbrains/compose/resources/ResourceState.blocking.kt

@ -5,12 +5,43 @@ import kotlinx.coroutines.runBlocking
@Composable @Composable
internal actual fun <T> rememberResourceState( internal actual fun <T> rememberResourceState(
key: Any, key1: Any,
getDefault: () -> T, getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T block: suspend (ResourceEnvironment) -> T
): State<T> { ): State<T> {
val environment = LocalComposeEnvironment.current.rememberEnvironment() val environment = LocalComposeEnvironment.current.rememberEnvironment()
return remember(key, environment) { return remember(key1, environment) {
mutableStateOf(
runBlocking { block(environment) }
)
}
}
@Composable
internal actual fun <T> rememberResourceState(
key1: Any,
key2: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T> {
val environment = LocalComposeEnvironment.current.rememberEnvironment()
return remember(key1, key2, environment) {
mutableStateOf(
runBlocking { block(environment) }
)
}
}
@Composable
internal actual fun <T> rememberResourceState(
key1: Any,
key2: Any,
key3: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T> {
val environment = LocalComposeEnvironment.current.rememberEnvironment()
return remember(key1, key2, key3, environment) {
mutableStateOf( mutableStateOf(
runBlocking { block(environment) } runBlocking { block(environment) }
) )

29
components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceState.kt

@ -10,7 +10,34 @@ import androidx.compose.runtime.State
*/ */
@Composable @Composable
internal expect fun <T> rememberResourceState( internal expect fun <T> rememberResourceState(
key: Any, key1: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T>
/**
* 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 <T> rememberResourceState(
key1: Any,
key2: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T>
/**
* 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 <T> rememberResourceState(
key1: Any,
key2: Any,
key3: Any,
getDefault: () -> T, getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T block: suspend (ResourceEnvironment) -> T
): State<T> ): State<T>

2
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 { fun stringResource(resource: StringResource, vararg formatArgs: Any): String {
val resourceReader = LocalResourceReader.current val resourceReader = LocalResourceReader.current
val args = formatArgs.map { it.toString() } val args = formatArgs.map { it.toString() }
val str by rememberResourceState(resource, { "" }) { env -> val str by rememberResourceState(resource, args, { "" }) { env ->
loadString(resource, args, resourceReader, env) loadString(resource, args, resourceReader, env)
} }
return str return str

23
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) 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 @Test
fun testLoadStringResource() = runTest { fun testLoadStringResource() = runTest {
assertEquals("Compose Resources App", getString(TestStringResource("app_name"))) assertEquals("Compose Resources App", getString(TestStringResource("app_name")))

2
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 @Composable
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
val resourceReader = LocalResourceReader.current 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 path = resource.getPathByEnvironment(env)
val fontBytes = resourceReader.read(path) val fontBytes = resourceReader.read(path)
Font(path, fontBytes, weight, style) Font(path, fontBytes, weight, style)

37
components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt

@ -8,13 +8,44 @@ import androidx.compose.runtime.remember
@Composable @Composable
internal actual fun <T> rememberResourceState( internal actual fun <T> rememberResourceState(
key: Any, key1: Any,
getDefault: () -> T, getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T block: suspend (ResourceEnvironment) -> T
): State<T> { ): State<T> {
val environment = LocalComposeEnvironment.current.rememberEnvironment() val environment = LocalComposeEnvironment.current.rememberEnvironment()
val state = remember(key) { mutableStateOf(getDefault()) } val state = remember(key1) { mutableStateOf(getDefault()) }
LaunchedEffect(key) { LaunchedEffect(key1) {
state.value = block(environment)
}
return state
}
@Composable
internal actual fun <T> rememberResourceState(
key1: Any,
key2: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T> {
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 <T> rememberResourceState(
key1: Any,
key2: Any,
key3: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T> {
val environment = LocalComposeEnvironment.current.rememberEnvironment()
val state = remember(key1, key2, key3) { mutableStateOf(getDefault()) }
LaunchedEffect(key1, key2, key3) {
state.value = block(environment) state.value = block(environment)
} }
return state return state

Loading…
Cancel
Save