Change language programmatically (Android N 7.0 - API 24)

Create a new class extends ContextWrapper

public class MyContextWrapper extends ContextWrapper {
    public MyContextWrapper(Context base) {
        super(base);
    }

    @TargetApi(Build.VERSION_CODES.N)
    public static ContextWrapper wrap(Context context, Locale newLocale) {
        Resources res = context.getResources();
        Configuration configuration = res.getConfiguration();

        if (VersionUtils.isAfter24()) {
            configuration.setLocale(newLocale);

            LocaleList localeList = new LocaleList(newLocale);
            LocaleList.setDefault(localeList);
            configuration.setLocales(localeList);

            context = context.createConfigurationContext(configuration);

        } else if (VersionUtils.isAfter17()) {
            configuration.setLocale(newLocale);
            context = context.createConfigurationContext(configuration);

        } else {
            configuration.locale = newLocale;
            res.updateConfiguration(configuration, res.getDisplayMetrics());
        }

        return new ContextWrapper(context);
    }
}

override Activity's attachBaseContext method

@Override
protected void attachBaseContext(Context newBase) {
    Locale languageType = LanguageUtil.getLanguageType(mContext);
    super.attachBaseContext(MyContextWrapper.wrap(newBase, languageType));
}

finish the activity and start it again,new locale will become effective.

demo:https://github.com/fanturbo/MultiLanguageDemo


I faced this issue when i started to target SDK 29. The LocaleHelper that i created worked on every version from my min SDK(21) to 29 but specifically on Android N it didn't use to work. So after searching many stack overflow answers and visiting https://issuetracker.google.com/issues/37123942 , i managed to find/modify a solution which is now working on all android versions.

So, first i took help of https://gunhansancar.com/change-language-programmatically-in-android/ to get a locale helper. Then i modified it to my needs like this:

 object LocaleHelper {
    const val TAG = "LocaleHelper"

    fun updateLocale(base: Context): Context {
        Log.e(TAG, "updateLocale: called");
        val preferenceHelper = PreferenceHelper(base)
        preferenceHelper.getStringPreference(PreferenceHelper.KEY_LANGUAGE).let {
            return if (it.isNotEmpty()) {
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
                    updateResources(base, it)
                } else {
                    updateResourcesLegacy(base, it)
                }
            } else {
                base
            }
        }
    }

    private fun updateResources(base: Context, language: String): Context{
        val loc = Locale(language)
        Locale.setDefault(loc)
        val configuration = base.resources.configuration
        configuration.setLocale(loc)
        return base.createConfigurationContext(configuration)
    }

    @Suppress("DEPRECATION")
    private fun updateResourcesLegacy(base: Context, language: String): Context{
        val locale = Locale(language)
        Locale.setDefault(locale)
        val configuration = base.resources.configuration
        configuration.locale = locale
        configuration.setLayoutDirection(locale)
        base.resources.updateConfiguration(configuration, base.resources.displayMetrics)
//                    Log.e(TAG, "updateLocale: returning for below N")
        return base
    }
}

Then, in the base activity, override the attachBaseContext method like:

   /**
     * While attaching the base context, make sure to attach it using locale helper.
     * This helps in getting localized resources in every activity
     */
    override fun attachBaseContext(newBase: Context?) {
        var context = newBase
        newBase?.let {
            context = LocaleHelper.updateLocale(it)
        }
        super.attachBaseContext(context)
    }

But i found that this used to work on Nougat and above and not in marshmallow and lollipop. So, I tried this:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //Update the locale here before loading the layout to get localized strings.
        LocaleHelper.updateLocale(this)

In the oncreate of base activity i called this method. And it started working. But there were instances where if we kill the app from recent tasks and then start it, the first screen wasn't localized. So to tackle that, i created a static temp variable in app class that held the value for initial app instance.

companion object {
        val TAG = "App"
        var isFirstLoad = true
    }

And in my first screen of app, i did this in oncreate:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //Need to call this so that when the application is opened for first time and we need to update the locale.
        if(App.isFirstLoad) {
            LocaleHelper.updateLocale(this)
            App.isFirstLoad = false
        }
        setContentView(R.layout.activity_dash_board)

Now i tested on redmi 4a (Android 7) on which it wasn't working previously, emulators of sdk 21,23,27,29 and in redmi note 5 pro and pixel devices. In all these, the in app language selection works properly.

Sorry for the long post, but please do suggest for an optimized way! Thanks!

EDIT 1: So i faced this issue where it was not working on android 7.1 (sdk 25). I asked gunhan for help and then made this change.

override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
    super.applyOverrideConfiguration(LocaleHelper.applyOverrideConfiguration(baseContext, overrideConfiguration))
}

I added this function in my LocaleHelper:

fun applyOverrideConfiguration(base: Context, overrideConfiguration: Configuration?): Configuration? {
    if (overrideConfiguration != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        val uiMode = overrideConfiguration.uiMode
        overrideConfiguration.setTo(base.resources.configuration)
        overrideConfiguration.uiMode = uiMode
    }
    return overrideConfiguration
}

And now it's finally working. Thanks Gunhan!