I was making app's language able to be changed by users. So, I added language configuration thing to my app. I added spinner to configuration screen, and spinner has list of language (Auto, English, Chinese, etc). I save selected item's position in shared preference (default value is 0 which is Auto). In every activity class, I add override method attachBaseContext(newBase: Context). This code is how I override attachBaseContext
override fun attachBaseContext(newBase: Context) {
val shared = newBase.getSharedPreferences("configuration",Context.MODE_PRIVATE)
val position = shared?.getInt("Language",0) ?: 0
super.attachBaseContext(LocaleManager.langChange(newBase,position)
and this is LocaleManager
object LocaleManager {
fun langChange(context: Context, position: Int): ContextWrapper {
var lang: String = if(position < StaticStore.lang.size)
StaticStore.lang[position]
else
StaticStore.lang[0]
if(lang == "")
lang = Resources.getSystem().configuration.locales.get(0).language
val config = context.resources.configuration
if(lang != "") {
val loc = Locale(lang)
Locale.setDefault(loc)
config.setLocale(loc)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSystemLocale(config,loc)
} else {
setSystemLocaleLegacy(config,loc)
}
}
var c = context
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
c = context.createConfigurationContext(config)
} else {
context.resources.updateConfiguration(config,context.resources.displayMetrics)
}
return ContextWrapper(c)
}
private fun setSystemLocaleLegacy(config: Configuration, loc: Locale) {
config.locale = loc
}
@TargetApi(Build.VERSION_CODES.N)
fun setSystemLocale(config: Configuration, loc: Locale) {
config.setLocale(loc)
}
}
StaticStore is java class which stores static variables. In StaticStore,
public static String[] lang = {"","en","zh","ja"};
This way worked fine when I was using Java. I thought converting my Android project to Kotlin will be good, so I converted activity classes first. But for some reason, this way isn't working now.
I searched about this problem for hours. Tried these answers and didn't work
https://stackoverflow.com/a/56251029/12781977 (Sets locale in class which extends Application)
https://stackoverflow.com/a/40849142/12781977 (This is similar with my code, but it uses LocaleList, not Locale)
But fortunately, I tried this way and it finally worked
override fun attachBaseContext(newBase: Context) {
val shared = newBase.getSharedPreferences(StaticStore.CONFIG, Context.MODE_PRIVATE)
val lang = shared?.getInt("Language",0) ?: 0
val config = Configuration() //Create new Configuration
var language = StaticStore.lang[lang]
if(language == "")
language = Resources.getSystem().configuration.locales.get(0).toString()
config.setLocale(Locale(language))
applyOverrideConfiguration(config) //Manually override configuration using "config"
super.attachBaseContext(LocaleManager.langChange(newBase,shared?.getInt("Language",0) ?: 0))
}
It doesn't print any error when I open specific activity first time. Reopening same activity (like go back to main screen and re-enter to configuration screen) prints error
2020-01-26 11:26:55.619 28216-28216/com.mandarin.myapp E/AppCompatDelegate: updateForNightMode. Calling applyOverrideConfiguration() failed with an exception. Will fall back to using Resources.updateConfiguration()
java.lang.IllegalStateException: Override configuration has already been set
at android.view.ContextThemeWrapper.applyOverrideConfiguration(ContextThemeWrapper.java:99)
at androidx.appcompat.app.AppCompatDelegateImpl.updateForNightMode(AppCompatDelegateImpl.java:2281)
at androidx.appcompat.app.AppCompatDelegateImpl.applyDayNight(AppCompatDelegateImpl.java:2170)
at androidx.appcompat.app.AppCompatDelegateImpl.attachBaseContext(AppCompatDelegateImpl.java:334)
at androidx.appcompat.app.AppCompatActivity.attachBaseContext(AppCompatActivity.java:98)
at com.mandarin.myapp.ConfigScreen.attachBaseContext(ConfigScreen.kt:371)
at android.app.Activity.attach(Activity.java:6598)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2580)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
I understand this error, but I have to use applyOverrideConfiguration(config) to solve locale setting problem. Weird thing is that even though this error is printed, app won't crash, but it disturbs me a lot.
Anyway, because of this error, I tried to use applyOverrideConfiguration(config) only once using static boolean. It can change language fine when I open activity first time. But when I reopen same activity, it shows default language again (English is device's language). So this means that only applyOverrideConfiguration(config) can change language...
Actually I don't need to call this method when super.attachBaseContext(LocaleManager.langChange(newBase,position) working fine, but it's not working for some reason.
Are there any ways to use applyOverrideConfiguration without printing error? Or are there any ways to solve changing language problem?