For compose multiplatform, is this a good pattern ...
# multiplatform
j
For compose multiplatform, is this a good pattern to have for localize things?
Copy code
interface MyStrings {
    val actionSave: MyTextResource
    val actionEdit: MyTextResource
    val actionLogout: MyTextResource
    val actionRegister: MyTextResource
    val actionOk: MyTextResource
    val actionCancel: MyTextResource
    val actionLogin: MyTextResource
    val actionClose: MyTextResource
}
MyTextResource is a sealed interface to support strings, annotations, urls, plurals and whatever type each platform will end up having in future. I am using it in combination with MyText composable enforce everywhere. And then depending on current Locale I am switch implementation for each language we support. I think it semi aligns with approach of this library https://github.com/adrielcafe/lyricist I am not yet confident which direction to use as Jetbrains not very clear on their roadmap of future things of resources, including images and such. Hence doing my own wrappers for images, locales and such to be sure I can easy change thing without refactoring the entire app πŸ˜„ Sample usage:
Copy code
MyText(
                    text = MyApp.strings.actionLogin,
                    style = MyApp.typography.button,
                )
MyApp is an object with @ReadOnlyComposables mapped to LocalCompositions.
p
I don't see any wrong doing. As long you store the Strings in a database or some persistent mean. And not have them as constants
c
What's wrong with having the strings as constants? The DroidKaigi app uses constants.
p
The memory consumption. Usually the class loader loads the full class on ram
A considerable small amount is ok. But once it starts growing too much the RAM consumption effects might be noticeable
j
@Pablichjenkov Hmm what is large amount? πŸ™‚ At the moment feels very overkill storing all locales in a database, would then need to open the translations to late when open the app kind a, as taking time to load database. Will see wrong translations in beginning open app.
Also curious how does this differ from resource("strings.xml") in Jetbrains resources using Android XML strings?
p
Traditional Android XML is a regular file, it is persistence storage with some optimizations to retrieve the data faster but persistent at the end of the day.
Perhaps not a database, you are correct in that it might be an overkill. Any simple form of persistence like a simple file should work. Is just to avoid that full loading of all strings constants in RAM.
j
Hmm yeah do you know if https://github.com/adrielcafe/lyricist or Moko resources doing this? πŸ™‚
p
In terms of the RAM consumption, not sure the latest amount OSes assign to an application process but if your strings take ~20% of that amount I would say is a lot
j
For now I think will not even be close, but its a good point πŸ™‚ Especially since I do using this in LocalComposition so I can access it all the time πŸ˜„
πŸ‘ 1
p
I would have to check. But most likely they do. I think a simple XML, json file should be enough. Some parsing optimizations are usually applied to this files.
j
I do know that Lyrsisct have a XML processor, just not sure what they use default πŸ™‚ I do like they allow you mixing stuff at least πŸ™‚
p
I might take a look more in deep later
j
But I guess with my current approach I can also mixing Jetbrains strings.xml resource with what I have actually πŸ˜› I do like this for Android:
Copy code
class AndroidString(
    @StringRes val stringResId: Int,
    private vararg val formatArgs: Any
) : TextResource {
    @Composable
    override fun getString(): String {
        val resources = LocalContext.current.resources
        return resources.getString(stringResId, *formatArgs)
    }
}

class AndroidPlural(
    @PluralsRes val pluralResId: Int,
    private val count: Int,
    private vararg val formatArgs: Any
) : TextResource {
    @Composable
    override fun getString(): String {
        val resources = LocalContext.current.resources
        return resources.getQuantityString(pluralResId, count, formatArgs)
    }
}
Will maybe give it a go using resource("path.xml") or such. Any ideas is welcome πŸ™‚
p
Using jetbrains resources sounds good. You would just need a fast XML parser/reader.
Do they provide that function
getString()
and
getQuantityString()
? If so you are done.
j
Not sure, need to re-investigate their source code in iOS vs Android targets. It has to work without Context, then safe.
p
Ah ok
j
https://github.com/JetBrains/compose-multiplatform/blob/master/components%2Fresources%2Fdemo%2Fshared%2Fsrc%2FcommonMain%2Fkotlin%2Forg%2Fjetbrains%2Fcompose%2Fresources%2Fdemo%2Fshared%2FStringRes.kt Looks like they generate a new Res.strings reference from xml. And then custom stringResource composable. Could work with my solution combined I think. Need to check jetbrains source code more closely. But looks promising as I can mix my current solution with this and test which is best.
https://github.com/JetBrains/compose-multiplatform/blob/master/components%2Fresources%2Flibrary%2Fsrc%2FcommonMain%2Fkotlin%2Forg%2Fjetbrains%2Fcompose%2Fresources%2FStringResources.kt Looks like they use a generated StringResource class with resource id + string key. Then using that for string array and formatted string. However plural not yet supported. Looks a lot more optimized solution. Need to check more how it deals with Locale but looks like solved if put in correct values folder locale qualifier.
p
Great
j
@Pablichjenkov Oh seems like its only in master branch and not released. Today not having this πŸ˜„ So I guess next release 1.6.0 or whatever it will be. Hopefully gradle module supported, so I can stop entrypoint from shared module all the time πŸ˜„
p
😁 well almost there
j
Yeah I now remember why I went into this road, what they have now didnt exist then πŸ˜„
Looks like they kind of re-invent the Android R class but multiplatform. They call the new class Res, and loading bytes from different resources. If like XML they pre-parse it from generated code somehow and cache all the string keys for each Locale πŸ˜› Just hoping I will be able to access it from all my feature modules, or at least adding it into my core module all modules can access πŸ˜„
Ironic very similar approach I have done, but not generate any code, hardcoding it πŸ˜„
πŸ˜„ 1