Vlad
12/19/2023, 10:12 AMvide
12/19/2023, 10:32 AMVlad
12/19/2023, 10:34 AMVlad
12/19/2023, 10:35 AMvide
12/19/2023, 10:37 AMvide
12/19/2023, 10:38 AMstringResource
implementation is a composable function that will trigger recomposition of the parent when LocalConfiguration.current
changesPeter
12/19/2023, 10:44 AMVlad
12/19/2023, 10:44 AMStringDesc.localeType = StringDesc.LocaleType.Custom("en")
It does change selection of the localizations it fetches.
But seems like it doesn't actually change the locale returned via
Locale.current.language
Vlad
12/19/2023, 10:46 AMVlad
12/19/2023, 10:55 AMvide
12/19/2023, 10:55 AMvide
12/19/2023, 10:56 AMVlad
12/19/2023, 10:56 AMAppTheme {
var currentLanguage: String by mutableStateOf(
""
)
App()
}
Vlad
12/19/2023, 10:57 AMvide
12/19/2023, 10:57 AMlocalized()
function look like?Vlad
12/19/2023, 10:58 AMvide
12/19/2023, 10:59 AMvide
12/19/2023, 10:59 AMlocalized()
seems to be composable, so what does it read? If it would read the current locale from a state or composition local internally, you would trigger recomposition for all places you need toVlad
12/19/2023, 11:00 AMvide
12/19/2023, 11:00 AMvide
12/19/2023, 11:00 AMvide
12/19/2023, 11:00 AMVlad
12/19/2023, 11:00 AMVlad
12/19/2023, 11:01 AM@Composable
override fun getString(stringToken: StringTokens): String {
return translator?.translate(stringToken.translationKey)
?: stringResource(stringToken.stringResource)
}
So technically every label delivered through my own composable which delegates the call to the Mokovide
12/19/2023, 11:01 AMVlad
12/19/2023, 11:02 AMVlad
12/19/2023, 11:03 AMvide
12/19/2023, 11:04 AMvide
12/19/2023, 11:05 AMLocalConfiguration.current
is read in the resources()
function, but it's not actually usedvide
12/19/2023, 11:05 AMvide
12/19/2023, 11:05 AMstaticCompositionLocalOf
and change its value.)Vlad
12/19/2023, 11:09 AMVlad
12/19/2023, 11:19 AMvar currentLanguageState: String by mutableStateOf(
""
)
override fun updateLocale(language: String) {
currentLanguageState = language
}
@Composable
override fun getString(stringToken: StringTokens): String {
currentLanguageState
return stringResource(stringToken.stringResource)
}
Yes, basically doing even something as simple as this do the thing.
Now I can even easily connect flow from the database which will be changing the language state every time it changed.
This is not pure compose way, because I added OOP abstraction for my resources provider
Thank you.Alex Vanyo
12/19/2023, 6:34 PMLocale.current
- they aren’t snapshot state aware if the current locale changes.Stylianos Gakis
01/22/2024, 1:37 PMLocalConfiguration.current
should be the right approach to get a recomposition when the locale changes.
I got a function like this
@Composable
@ReadOnlyComposable
fun getLocale(): Locale {
val configuration = LocalConfiguration.current
SideEffect { Log.d("", "configuration changed") }
return ConfigurationCompat.getLocales(configuration).get(0) ?: LocaleListCompat.getAdjustedDefault()[0]!!
}
But it seems like this is not properly giving me the latest response in all Android API versions, with all other things being the same. The log just does not run at all, and I do not get the latest value back from this composable
On API 33 for example changing the locale by calling AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags("some language tag here"))
I get back the latest locale from this composable and the log triggers too. Doing the same on API 24, 28, 29, 30, 31 and 32 (what I've tested so far) I do not get the new value back in composition from my getLocale()
here, but my app does seem to properly change language. I have no idea how exactly this is happening 🫣
All this with android:configChanges="locale"
being there of course. Without it you simply get a activity recreation and all this works, but I'd like to not have to do this if possible.Bwaim
01/22/2024, 2:40 PMActivity
inherits from AppCompatActivity
?Albert Chang
01/22/2024, 3:06 PMAppCompatDelegate.setApplicationLocales
force recreates the activity below API 33, which is necessary to change app locales without framework API.
andorid:configChanges
only controls config changes initiated by system and it doesn’t prevent Acitivity.recreate()
calls.Stylianos Gakis
01/22/2024, 3:32 PMAppCompatActivity
So Albert, if it forces activity recreation, but I am not getting the new value, how does this interaction exactly play out? What should I be doing instead?Albert Chang
01/22/2024, 3:38 PMAlex Vanyo
01/22/2024, 6:21 PMActivity
isn’t recreated)
Through Activity.onConfigurationChanged
, and View.onConfigurationChanged
. Notably, they are independently called by the platform (one doesn’t directly depend on the other). Also notably, Compose updates through the View.onConfigurationChanged
path.
It might be the case that AppCompactDelegate
only calls through the Activity.onConfigurationChanged
, which leaves Compose out of date.
What happens if you call View.dispatchConfigurationChanged
on your root view, from your Activity.onConfigurationChanged
method? That might force any attached ComposeView
to be notified about the new Configuration
Stylianos Gakis
01/23/2024, 9:05 AM|locale
into my configChanges
of my activity
In my activity, I got my aforementioned getLocale()
and painting it on the center of the screen for debug purposes
setContent {
val locale = getLocale()
Box {
App()
Text(text = locale.toLanguageTag(), textAlign = TextAlign.Center, modifier = Modifier.align(Alignment.Center))
}
}
"before_dispatching.mov" video shows how changing the language (I call AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(language.toString()))
on click there)
Now the weird thing is that my app does in fact return the right strings afterwards. Perhaps since stringResource
tries to get those from LocalContext.resources.getString
that one is updated properly, while configuration doesn't know about this change?
Then the second video, "after_dispatching.mov", shows after I've applied your suggestion Alex like this:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val composeView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? ComposeView
composeView?.dispatchConfigurationChanged(newConfig)
}
that I do in fact get the new configuration this way.
So this absolutely must be a bug. I should probably file a new bug report right?Stylianos Gakis
01/23/2024, 9:51 AMconfigChanges
as suggested here has made me shoot myself in the foot more than once, last time that I remember it was with m3 bottom sheets here, I think there was some other case too, and now this.
I think it's a good idea to discourage people from using it in general when it seems like there's a lot of issues here and there.
I think for now I'd rather just not adding locale
to the list of those changes and just let it recreate the activity, which at least I now know will work correctly.Stylianos Gakis
01/23/2024, 9:56 AM