3

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?

Mandarin Smell
  • 391
  • 2
  • 17

0 Answers0