[android] How to refresh activity after changing language (Locale) inside application

My application users can change the language from the app's settings. Is it possible to change the language inside the application without having effect to general language settings ? This question of stackoverflow is very useful to me and i have tried it. After changing language newly created activities display with changed new language, but current activity and previously created activities which are in pause state are not updated.How to update activities ? I have also spent a lot of time trying to make the preference change to be applied immediately but didn't succeed. When application is restarted, all activities created again, so now language changed correctly.

android:configChanges="locale" 

also added in manifest for all activities. and also support all screen. Currently I have not done any thing in activity's onResume() method. Is there any way to refresh or update activity (without finish and starting again) ? Am I missing something to do in onResume() method?

This question is related to android

The answer is


For Android 4.2 (API 17), you need to use android:configChanges="locale|layoutDirection" in your AndroidManifest.xml. See onConfigurationchanged is not called over jellybean(4.2.1)


You can use recreate(); to restart your activity when Language change.

I am using following code to restart activity when language change:

SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
Configuration config = getBaseContext().getResources().getConfiguration();

String lang = settings.getString("lang_list", "");

if (! "".equals(lang) && ! config.locale.getLanguage().equals(lang)) {
      recreate();  //this is used for recreate activity
      Locale locale = new Locale(lang);
      Locale.setDefault(locale);
      config.locale = locale;
      getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
}

Call this method to change app locale:

public void settingLocale(Context context, String language) {

    Locale locale;

    Configuration config = new Configuration();

     if(language.equals(LANGUAGE_ENGLISH)) {

        locale = new Locale("en");

        Locale.setDefault(locale);

        config.locale = locale;

    }else if(language.equals(LANGUAGE_ARABIC)){

        locale = new Locale("hi");

        Locale.setDefault(locale);

        config.locale = locale;

    }

    context.getResources().updateConfiguration(config, null);

    // Here again set the text on view to reflect locale change

    // and it will pick resource from new locale

    tv1.setText(R.string.one); //tv1 is textview in my activity

}

Note: Put your strings in value and values- folder.


The way we have done it was using Broadcasts:

  1. Send the broadcast every time the user changes language
  2. Register the broadcast receiver in the AppActivity.onCreate() and unregister in AppActivity.onDestroy()
  3. In BroadcastReceiver.onReceive() just restart the activity.

AppActivity is the parent activity which all other activities subclass.


Below is the snippet from my code, not tested outside the project, but should give you a nice idea.

When the user changes the language

sendBroadcast(new Intent("Language.changed"));

And in the parent activity

public class AppActivity extends Activity {

    /**
     * The receiver that will handle the change of the language.
     */
    private BroadcastReceiver mLangaugeChangedReceiver;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // ...
        // Other code here
        // ...

        // Define receiver
        mLangaugeChangedReceiver = new BroadcastReceiver() {

            @Override
            public void onReceive(final Context context, final Intent intent) {
                startActivity(getIntent());
                finish();
            }
        };

        // Register receiver
        registerReceiver(mLangaugeChangedReceiver, new IntentFilter("Language.changed"));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // ...
        // Other cleanup code here
        // ...

        // Unregister receiver
        if (mLangaugeChangedReceiver != null) {
            try {
                unregisterReceiver(mLangaugeChangedReceiver);
                mLangaugeChangedReceiver = null;
            } catch (final Exception e) {}
        }
    }
}

This will also refresh the activity which changed the language (if it subclasses the above activity).

This will make you lose any data, but if it is important you should already have taken care of this using Actvity.onSaveInstanceState() and Actvity.onRestoreInstanceState() (or similar).

Let me know your thoughts about this.

Cheers!


This approach will work on all API level device.

  1. Use Base Activity for attachBaseContext to set the locale language and extend this activity for all activities

    open class  BaseAppCompactActivity() : AppCompatActivity() {
    
        override fun attachBaseContext(newBase: Context) {
            super.attachBaseContext(LocaleHelper.onAttach(newBase))    
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)           
        }
    }
    
  2. Use Application attachBaseContext and onConfigurationChanged to set the locale language

    public class MyApplication extends Application {
        private static MyApplication application;
    
        @Override
        public void onCreate() {
            super.onCreate();
        }
    
        public static MyApplication getApplication() {
            return application;
        }
    
        /**
         * overide to change local sothat language can be chnaged from android device  nogaut and above
         */
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(LocaleHelper.INSTANCE.onAttach(base));
        }
    
        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            setLanguageFromNewConfig(newConfig);
            super.onConfigurationChanged(newConfig);
        }
    
        /*** also handle chnage  language if  device language chnaged **/
        private void setLanguageFromNewConfig(Configuration newConfig){
            Prefs.putSaveLocaleLanguage(this,  selectedLocaleLanguage );
            LocaleHelper.INSTANCE.onAttach(this);
        }
    
  3. Use Locale Helper for handling language changes, this approach work on all device

    object LocaleHelper {
        private var defaultLanguage  :String = KycUtility.KYC_LANGUAGE.ENGLISH.languageCode
    
        fun onAttach(context: Context, defaultLanguage: String): Context {
            return setLocale(context, defaultLanguage)
        }
    
    
    
        fun setLocale(context: Context, language: String): Context {
            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                updateResources(context, language)
            } else updateResourcesLegacy(context, language)
    
        }
    
    
        @TargetApi(Build.VERSION_CODES.N)
        private fun updateResources(context: Context, language: String): Context {
            val locale = Locale(language)
            Locale.setDefault(locale)
    
            val configuration = context.getResources().getConfiguration()
            configuration.setLocale(locale)
            configuration.setLayoutDirection(locale)
    
            return context.createConfigurationContext(configuration)
        }
    
        private fun updateResourcesLegacy(context: Context, language: String): Context {
            val locale = Locale(language)
            Locale.setDefault(locale)
    
            val resources = context.getResources()
    
            val configuration = resources.getConfiguration()
            configuration.locale = locale
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                configuration.setLayoutDirection(locale)
            }
            resources.updateConfiguration(configuration, resources.getDisplayMetrics())
            return context
        }
    }
    

Since the string resources will have already been loaded for the existing locale, the activities already opened will not automatically display using strings from the new locale. The only way to solve this is to reload all of the strings and set them again on the views. Typically, a call to setContentView(...) will be able to cover this (depending on your Activity structure), but of course it has the side-effect of losing any view state you had.

public void onResume() {
    super.onResume();
    ...
    if (localeHasChanged) {
        setContentView(R.layout.xxx);
    }
    ...
}

You will probably not want to reload the views every single time in onResume(), but only when the locale has changed. Checking when to update the views (i.e. localeHasChanged) is a matter of propagating the locale change event to the previous activities. This could be done in many ways, such as using static singleton-esque state or persisting this event to storage.

You could also try to minimise the number of Activities that can be opened when you can change the locale, e.g. by having the selection be at one of the initial screens.


If I imagined that you set android:configChanges in manifest.xml and create several directory for several language such as: values-fr OR values-nl, I could suggest this code(In Activity class):

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button btn = (Button) findViewById(R.id.btn);
    btn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            // change language by onclick a button
             Configuration newConfig = new Configuration();
             newConfig.locale = Locale.FRENCH;
             onConfigurationChanged(newConfig);
        }
    });
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    getBaseContext().getResources().updateConfiguration(newConfig, getBaseContext().getResources().getDisplayMetrics());
    setContentView(R.layout.main);
    setTitle(R.string.app_name);

    // Checks the active language
    if (newConfig.locale == Locale.ENGLISH) {
        Toast.makeText(this, "English", Toast.LENGTH_SHORT).show();
    } else if (newConfig.locale == Locale.FRENCH){
        Toast.makeText(this, "French", Toast.LENGTH_SHORT).show();
    }
}

I tested this code, It is correct.


I solved my problem with this code

public void setLocale(String lang) {

        myLocale = new Locale(lang);
        Resources res = getResources();
        DisplayMetrics dm = res.getDisplayMetrics();
        Configuration conf = res.getConfiguration();
        conf.locale = myLocale;
        res.updateConfiguration(conf, dm);

        onConfigurationChanged(conf);
    }



    @Override
    public void onConfigurationChanged(Configuration newConfig) 
    {
        iv.setImageDrawable(getResources().getDrawable(R.drawable.keyboard));
        greet.setText(R.string.greet);
        textView1.setText(R.string.langselection);

        super.onConfigurationChanged(newConfig);

    }