What's the best way of caching string resources in...
# compose-web
p
What's the best way of caching string resources in Compose WEB (WASM). We are getting issues where labels are not being displayed because the async call to get the string resources doesn't complete in time before the rendering takes places. It seems that a call to Request URL is being made for every string, every time (this incidentally brings back ALL strings) I've tried using http caching via caching headers but even that (0 or 1ms) is too slow it seems. Is there a way of loading string resources once ? http://localhost/composeResources/myapp.composeapp.generated.resources/values/strings.commonMain.cvr
👍 1
o
p
HI yes, I'm already following that. I need a work around for now though. Sometimes in CMP WASM a call to stringResource(someResource) returns a blank string rather than the actual value, and the UI then shows no labels as a result. I've tried using
Copy code
Res.allStringResources
but when you use it that also brings back empty strings in WASM only:
Copy code
object AllResources {

    var allStringsCache: Map<String, String> = mutableMapOf()
    var allResourcesCache: Map<String, StringResource> = mutableMapOf()

    @OptIn(ExperimentalResourceApi::class)
    @Composable
    fun getAllResources(): Map<String, String> {
        return allStringsCache.ifEmpty {
            allResourcesCache = Res.allStringResources
            val all = allResourcesCache.map { stringRes ->
                stringRes.key to stringResource(stringRes.value)

            }.toMap()
            allStringsCache = all
            allStringsCache
        }.also {
            allStringsCache.forEach {
                println("Key: ${it.key}, Value: ${it.value}")
            }
        }
    }

    fun getStringResource(key: String): String {
        return allStringsCache[key] ?: ""
    }

    @Composable
    fun getStringResource(stringRes: StringResource): String {
        return allResourcesCache[stringRes.key]?.let { stringResource(it) } ?: ""
    }
}
I've created https://youtrack.jetbrains.com/issue/CMP-8122/stringResource-returns-blank-in-WASM as it's similar but not the same as the issue mentioned above.
o
A better workaround would be to use https://github.com/JetBrains/compose-multiplatform/blob/1b6e61592dd44bfc8747f429d0[…]nMain/kotlin/org/jetbrains/compose/resources/StringResources.kt You can call it in LaunchedEffect. By calling it, you'll warmup the internal strings caches. Then you can use the string resource as usual, by calling
stringResource(...)
, it will return a value from the cache.
p
Thanks, do you have an example ? I'm not sure what I should be passing to the constructor of StringResource()
o
something like this:
Copy code
LaunchedEffect(Unit) {
            Res.allStringResources.forEach {
                launch {
                    org.jetbrains.compose.resources.getString(it.value)
                }
            }
        }
p
Great thanks
This works great in Safari but not in Chrome 😞 composeApp.uninstantiated.mjs:241 JsException: Exception was thrown while running JavaScript code kotlinx.coroutines.error_$external_fun @ composeApp.uninstantiated.mjs:241 $func3999 @ f4d9617ac9f7f9ce3966.wasm:0x1c1c60 $func3632 @ f4d9617ac9f7f9ce3966.wasm:0x1b34e0 $func3568 @ f4d9617ac9f7f9ce3966.wasm:0x1b0d5c $func3709 @ f4d9617ac9f7f9ce3966.wasm:0x1b52eb $func3696 @ f4d9617ac9f7f9ce3966.wasm:0x1b45a0 $func3710 @ f4d9617ac9f7f9ce3966.wasm:0x1b54de $func3709 @ f4d9617ac9f7f9ce3966.wasm:0x1b5368 $func3731 @ f4d9617ac9f7f9ce3966.wasm:0x1b6556 $func3730 @ f4d9617ac9f7f9ce3966.wasm:0x1b62a3 $func3558 @ f4d9617ac9f7f9ce3966.wasm:0x1b0a13 $func2163 @ f4d9617ac9f7f9ce3966.wasm:0x18fd58 $func4009 @ f4d9617ac9f7f9ce3966.wasm:0x1c2313 $func10866 @ f4d9617ac9f7f9ce3966.wasm:0x2d1706 $func10875 @ f4d9617ac9f7f9ce3966.wasm:0x2d1a00 $func10874 @ f4d9617ac9f7f9ce3966.wasm:0x2d19dc $func11040 @ f4d9617ac9f7f9ce3966.wasm:0x2d786c $func8248 @ f4d9617ac9f7f9ce3966.wasm:0x26bcaa $__callFunction___Double_->Unit_ @ f4d9617ac9f7f9ce3966.wasm:0x1c6ed1 (anonymous) @ composeApp.uninstantiated.mjs:322 requestAnimationFrame org.jetbrains.skiko.w3c.requestAnimationFrame_$external_fun @ composeApp.uninstantiated.mjs:480 $func8251 @ f4d9617ac9f7f9ce3966.wasm:0x26c1c6 $func11184 @ f4d9617ac9f7f9ce3966.wasm:0x2df765 $func10941 @ f4d9617ac9f7f9ce3966.wasm:0x2d427c $func10927 @ f4d9617ac9f7f9ce3966.wasm:0x2d3a8d $func10831 @ f4d9617ac9f7f9ce3966.wasm:0x2cef06 $func10829 @ f4d9617ac9f7f9ce3966.wasm:0x2cea23 $func11167 @ f4d9617ac9f7f9ce3966.wasm:0x2df198 $func11166 @ f4d9617ac9f7f9ce3966.wasm:0x2dea82 $func11172 @ f4d9617ac9f7f9ce3966.wasm:0x2df5f7 $func3876 @ f4d9617ac9f7f9ce3966.wasm:0x1bd0f1 $func3873 @ f4d9617ac9f7f9ce3966.wasm:0x1bce1f $func3874 @ f4d9617ac9f7f9ce3966.wasm:0x1bce90 $func3875 @ f4d9617ac9f7f9ce3966.wasm:0x1bcf8a $func11170 @ f4d9617ac9f7f9ce3966.wasm:0x2df3f9 $func11169 @ f4d9617ac9f7f9ce3966.wasm:0x2df32b $func2174 @ f4d9617ac9f7f9ce3966.wasm:0x190063 $func2163 @ f4d9617ac9f7f9ce3966.wasm:0x18fbd8 $func4009 @ f4d9617ac9f7f9ce3966.wasm:0x1c22a9 $func10861 @ f4d9617ac9f7f9ce3966.wasm:0x2d14f5 $func10875 @ f4d9617ac9f7f9ce3966.wasm:0x2d1a00 $func10865 @ f4d9617ac9f7f9ce3966.wasm:0x2d164e $func10864 @ f4d9617ac9f7f9ce3966.wasm:0x2d15f0 $func2174 @ f4d9617ac9f7f9ce3966.wasm:0x190063 $func2163 @ f4d9617ac9f7f9ce3966.wasm:0x18fbd8 $func4009 @ f4d9617ac9f7f9ce3966.wasm:0x1c22a9 $func4070 @ f4d9617ac9f7f9ce3966.wasm:0x1c3a2c $func4122 @ f4d9617ac9f7f9ce3966.wasm:0x1c4747 $__callFunction____->Unit_ @ f4d9617ac9f7f9ce3966.wasm:0x18e40e (anonymous) @ composeApp.uninstantiated.mjs:105 Promise.then (anonymous) @ composeApp.uninstantiated.mjs:243 kotlinx.coroutines.__callJsClosure_(()->Unit) @ composeApp.uninstantiated.mjs:244 $func4123 @ f4d9617ac9f7f9ce3966.wasm:0x1c475a $func4069 @ f4d9617ac9f7f9ce3966.wasm:0x1c399f $func4060 @ f4d9617ac9f7f9ce3966.wasm:0x1c3753 $func4103 @ f4d9617ac9f7f9ce3966.wasm:0x1c4184 $func4005 @ f4d9617ac9f7f9ce3966.wasm:0x1c1e7b $func4007 @ f4d9617ac9f7f9ce3966.wasm:0x1c1f41 $func4020 @ f4d9617ac9f7f9ce3966.wasm:0x1c2730 $func3562 @ f4d9617ac9f7f9ce3966.wasm:0x1b0aa0 $func3563 @ f4d9617ac9f7f9ce3966.wasm:0x1b0c09 $func3564 @ f4d9617ac9f7f9ce3966.wasm:0x1b0c32 $_initialize @ f4d9617ac9f7f9ce3966.wasm:0x452fc4 a @ composeApp.uninstantiated.mjs:644 await in a (anonymous) @ composeApp.mjs:7 await in (anonymous) i.a @ async module:49 735 @ lazy strict namespace object:13 i @ bootstrap:19 (anonymous) @ startup:4 (anonymous) @ startup:4 (anonymous) @ universalModuleDefinition:9 (anonymous) @ universalModuleDefinition:1 composeApp.uninstantiated.mjs:108 * ERROR !!!! CancellationException: Parent job is Cancelling
o
which Chrome version do you use?
p
Version 136.0.7103.93 (Official Build) (arm64) on MACOS It always fails on this line: org.jetbrains.compose.resources.getString(it.value) A work around is to do it synchronously using stringResource() but obviously that's not great from a UI POV.
o
Do you have a lot of strings in the file? What if you try something like this:
Copy code
LaunchedEffect(Unit) {
            Res.allStringResources.forEach {
                // launch { // no launch for now
                    org.jetbrains.compose.resources.getString(it.value)
                // }
            }
        }
Call it somewhere at the initialization. The sooner - the better
p
Thanks again. Yes, about 350 strings in the file. I've tried running the code as the first line of the first composable in the application, and it always fails trying to retrieve the first string.
Incidentally the only reason I'm trying to pre-load these strings is to get around an issue where the strings sometimes return as blanks. This doesn't happen in Safari, only in Chrome, so it seems that Chrome has some issues running async code. I'm on the version 1.10.2 of coroutines by the way.
o
I tried to run the snippet above in a minimal project, then disabled the internet connection, opened a page where strings should be displayed, and they were shown as expected - so caching works. _ Perhaps, do you have plural strings
PluralStringResource
or
StringArrayResource
?