If I handle config changes manually (no activity recreation) and I want to override the system langu...
j
If I handle config changes manually (no activity recreation) and I want to override the system language the only way I’ve found is by using
updateConfiguration(config, displayMetrics)
, which works fine but happens to be deprecated 😅. All other ways that I know of rely on activity recreation (wrapping the context and using
createConfigurationContext
, using
attachBaseContext
, using
applyOverrideConfiguration
). Is there a non-deprecated way of doing this (without recreating the activity)? (I think overwriting the language is considered not being the best practice, but it is quite common and many clients want it 🤷‍♂️)
a
Copy code
override fun attachBaseContext(newBase: Context) {
        val config = Configuration()
        config.setLocale(<http://Locale.US|Locale.US>)
        applyOverrideConfiguration(config)
        super.attachBaseContext(newBase)
    }
This works for me. The activity will not be re-created.
j
But once this has been called and the language is set, if you want to change the language (the user selects a new one in the app's UI) how do you do it, without recreating the activity?
a
Recreating the activity and using
updateConfiguration()
are the only ways to manually change configuration so I don't think you have any other choice. See here for how it is done in the AppCompat library.
Btw, this article mentions the reason why
updateConfiguration()
is deprecated.
AppCompat needs to change the Activity’s resources configuration to enable “night mode”. The problems with WebView stemmed from using a now deprecated method: 
Resources.updateConfiguration()
 to achieve that. Unfortunately WebView doesn’t work very well with that method (hence the deprecation).
j
Ohh thank you! Very interesting links 🙂 How would this affect to compose, especially if manually handling config changes? For example, it seems we would not need to update the configuration to support night mode, but I haven’t looked into dark mode yet.
And is there a chance we can get (not necessarily soon) new APIs to deal with these things now that theming, resources and config changes can be so different? Especially not to rely on a deprecated method that could eventually stop working
a
The reason why recreation is needed is that in traditional view-based UI framework, there is no out-of-the-box way of reacting to configuration changes. In Compose UI, if you use
AmbientConfiguration
(which is used internally in
stringResource()
), the composer will automatically recompose the necessary parts when configuration changes thus recreation is not needed. If you really don't want the recreation and your app is compose-only, I think you can provide your own
AmbientConfiguration
and
AmbientContext
(I haven't tried, though).
j
I tested this and it worked. It feels much better than using updateConfiguration 😄 This is the code that I use:
Copy code
val configuration = AmbientConfiguration.current
configuration.setLocale(Locale(myLocale))
val context = AmbientContext.current.createConfigurationContext(configuration)
Providers(AmbientContext provides context) {
    Providers(AmbientConfiguration provides configuration) {
        ...
    }
}
Setting only the configuration did not work. Setting only the context did work. Is there any use on overriding the configuration (for changing the language)? Maybe for RTL?
a
Define your desired locale as a MutableState and your change to it will automatically be adapted. To set layout direction, you have to also provide
AmbientLayoutDirection
. Btw,
Providers
accepts varargs so you can simply write:
Copy code
Providers(
    AmbientContext provides context,
    AmbientConfiguration provides configuration,
    AmbientLayoutDirection provides TextUtils.getLayoutDirectionFromLocale(locale)
) {
    ...
}
j
Oh, much better with varargs 😄 Thank you!
Hi again! I’ve just realised that a bug I’ve had is caused by this:
AmbientContext provides context
. It breaks the navigation backstack: when I press back on any screen it exits the app instead of going to the previous screen. Setting configuration and layout direction does not cause this problem, but they don’t change the language either. Is there any workaround that can be done by remembering some state? And is this a navigation bug? (I have a minimal project to reproduce the issue if needed)
a
Attach the project then.
a
You are creating a new context that is not an activity (which is also an
OnBackPressedDispatcherOwner
) so NavHost cannot find an
OnBackPressedDispatcher
. Since you are using navigation 1.0.0-alpha06 there seems no workaround. I suggest you upgrade to compose 1.0.0-alpha12 and navigation 1.0.0-alpha07 as there has been a huge API change and you can provide your own
OnBackPressedDispatcherOwner
in navigation 1.0.0-alpha07 like this:
Copy code
Providers(
    LocalOnBackPressedDispatcherOwner.asProvidableCompositionLocal() provides this@MainActivity,
    LocalConfiguration provides config,
    LocalLayoutDirection provides layoutDirection,
    LocalContext provides context
) {
    val navController = rememberNavController()
    ...
}
j
Thanks, with this it works! I was waiting for support from koin but I’ll have to adapt it to work with alpha12
Sorry to bother again with this 😔 I’ve realised I have a new language problem since I migrated to alpha12: when I change the language all the content gets updated except for the text of the BottomBar labels.  I’ve updated the project, and with the current code this problem occurs. If I uncomment (in MainActivity) the code that passes a languageChange lambda to the layout and calls it from the button, and comment the coroutine code, the BottomBar labels do get updated. Maybe it has to do with compose thinking there is no reason to recompose the bottom bar, but why does the rest get updated, then?
a
First, your usage of coroutine is wrong. You are launching a new job on every recomposition. If you want to launch a single job in the life time of a composable, you should use
LaunchedEffect
. As for the problem, it looks like a bug to me. It seems that usage of a
CompositionLocal
created by
staticCompositionLocalOf
in the
topBar
and
bottomBar
of
Scaffold
is not recomposed when the value changes (`CompositionLocal`s created by
compositionLocalOf
are not affected, though). You might want to file a bug.
j
Thank you! I never start coroutines from composables and I went for the first that sort of worked, but it is good to know of LaunchedEffect. In the bug I filed I updated the project with LaunchedEffect. I filed it in “Jetpack Compose -> UI Libraries”. Not sure if it should be in runtime instead…
625 Views