Jacek
09/13/2023, 5:47 AM//CommonMain
LaunchedEffect(true) {
viewModel.effect.collect { effect ->
when (effect) {
is ConnectingContract.Effect.Error -> {
snackbarHostState.showSnackbar(
message = "Error occured",
)
}
}
}
}
I want to change hardcoded text to resource using moko
library. Unfortunately, I cannot use the fun stringResources()
in LaunchedEffect
, nor do I have access to Context
.
How to achieve this?
I have prepared an expect class Strings
which in the Android implementation takes context
.
expect class Strings {
fun stringResource(string: StringResource) : String
}
actual class Strings(private val activity: Context) {
actual fun stringResource(string: StringResource): String {
return string.getString(activity)
}
}
But I have to pass it to every screen...
// Android Main
val context = LocalContext.current
val strings = getKoin().get<Strings> {
parametersOf(context)
}
ConnectingScreen(
viewModel = koinViewModel(),
strings
)
jamshedalamqaderi
09/13/2023, 5:50 AMMR.strings.hellow.localized()
Jacek
09/13/2023, 5:53 AMUnresolved reference: localized
Under localized()
is this code:
package dev.icerock.moko.resources.desc
actual data class ResourceStringDesc actual constructor(
val stringRes: StringResource
) : StringDesc, Parcelable {
override fun localized(): String = stringRes.localized(
locale = StringDesc.localeType.currentLocale
)
}
jamshedalamqaderi
09/13/2023, 5:54 AMlocalized()
is composable functionJacek
09/13/2023, 5:55 AMLaunchedEffect
jamshedalamqaderi
09/13/2023, 5:55 AMVlad
09/15/2023, 12:19 PMVlad
09/15/2023, 12:20 PMJacek
09/15/2023, 12:24 PMstringResurce(string: StringDesc)
a line before the LaunchedEffect
bacause this StringDesc
i'm getting from LaunchedEffect
Yes, I've heard of CompositionLocal
but I don't know how to use it yet 🙂Vlad
09/15/2023, 12:33 PMval LocalAppResourcesProvider = staticCompositionLocalOf<ResourcesProvider?> {
null
}
You just declare anything as CompositionLocal, here ResourceProvider my class.
Somewhere on top of the tree you initialize the actual value (if it can't be initialized at the declaration time).
val resourcesProvider: ResourcesProvider = provideResourceProvider()
CompositionLocalProvider(LocalAppResourcesProvider provides resourcesProvider) {
MyApp()
}
And anywhere down the tree you can access it as simple as:
val resourrsesProvided LocalAppResourcesProvider.current ?: error("App resources provider is not provided for local")
Adding a little bit sugar we can create shortcut to the LocalAppResourcesProvider.current
via defining object:
object AppRes {
val provider: ResourcesProvider
@Composable
get() = LocalAppResourcesProvider.current ?: error("App resources provider is not provided for local")
}
And now I can access it anywhere in the app as simple as AppRes.provider.getString()
Even further, we can go more with the Compose way and create our own Composable function to fetch the resource:
@Composable
fun stringResource(stringToken: StringTokens): String {
return AppRes.provider.getString(stringToken)
}
And now we have setup to fetch the strings which is fully incapsulates logic how exactly the string is fetchedVlad
09/15/2023, 12:38 PMJacek
09/15/2023, 12:56 PM// commonMain - no context
LaunchedEffect(true) {
viewModel.effect.collect { effect ->
when (effect) {
ConnectingContract.Effect.AutoNavigateForward -> navigateToApp()
is ConnectingContract.Effect.ErrorDesc -> {
val msg: StringDesc = effect.msg
val msgString: String = strings.stringResource(msg)
snackbarHostState.showSnackbar(msgString)
}
}
}
}
For this reason, StringDesc
is passed as the Effect
argument of viewModel
. StringDesc
is a cross-platform variant of StringResource
.
To make a String
from StringDesc
, in Android I have to use the current activity context
(to change the language)
With CompositionLocal I created:
val LocalStrings = staticCompositionLocalOf<Strings?> { null }
I initialize this in Activity
val context = LocalContext.current
CompositionLocalProvider(LocalStrings provides Strings(context)) {
App()
}
Then I can use it like
val strings = LocalStrings.current ?: throw Exception()
Nice.. I don't like having to throw an exception when null, but maybe I'll come up with something
Thanks!Vlad
09/15/2023, 1:59 PMVlad
09/15/2023, 2:00 PMVlad
09/15/2023, 2:00 PMJacek
09/15/2023, 2:38 PMalex009
09/17/2023, 4:56 PMexpect class ResourceReader {
fun get(resource: StringResource): String
}
@Composable
expect fun getResourceReader(): ResourceReader
2. implement actual for android:
actual class ResourceReader(private val context: Context) {
actual fun get(resource: StringResource): String {
return resource.getString(context)
}
}
@Composable
actual fun getResourceReader(): ResourceReader {
val context: Context = LocalContext.current
return remember(context) { ResourceReader(context) }
}
3. implement actual for iOS:
actual class ResourceReader {
actual fun get(resource: StringResource): String {
return resource.localize()
}
}
@Composable
actual fun getResourceReader(): ResourceReader {
return remember { ResourceReader() }
}
Then you can do in commonMain:
val resourceReader = getResourceReader()
LaunchedEffect(true) {
viewModel.effect.collect { effect ->
when (effect) {
is ConnectingContract.Effect.Error -> {
snackbarHostState.showSnackbar(
message = resourceReader.get(effect.resource),
)
}
}
}
}