Search code examples
androidandroid-layoutlocalizationlocaleandroid-7.0-nougat

Android RTL issue in API 24 and higher on locale change


I was trying to change locale of app at runtime. It is working fine in Andorid below API level 24. But in API level 24 or greater the layout direction is not changing according to locale. Below is the code to change the locale at runtime. I have used LocaleHelper class as below

public class LocaleHelper {

    private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language";

    public static Context onAttach(Context context) {
        String lang = getPersistedData(context, Locale.getDefault().getLanguage());
        return setLocale(context, lang);
    }

    public static Context onAttach(Context context, String defaultLanguage) {
        String lang = getPersistedData(context, defaultLanguage);
        return setLocale(context, lang);
    }

    public static String getLanguage(Context context) {
        return getPersistedData(context, Locale.getDefault().getLanguage());
    }

    public static Context setLocale(Context context, String language) {
        persist(context, language);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return updateResources(context, language);
        }

        return updateResourcesLegacy(context, language);
    }

    private static String getPersistedData(Context context, String defaultLanguage) {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getString(SELECTED_LANGUAGE, defaultLanguage);
    }

    private static void persist(Context context, String language) {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = preferences.edit();

        editor.putString(SELECTED_LANGUAGE, language);
        editor.apply();
    }

    @TargetApi(Build.VERSION_CODES.N)
    private static Context updateResources(Context context, String language) {
        Locale locale = new Locale(language);
        Log.d("LocaleHelper", "language : "+language);
        Locale.setDefault(locale);

        Configuration configuration = context.getResources().getConfiguration();
        configuration.setLocale(locale);
        configuration.setLayoutDirection(locale);
        return context.createConfigurationContext(configuration);
    }

    @SuppressWarnings("deprecation")
    private static Context updateResourcesLegacy(Context context, String language) {
        Locale locale = new Locale(language);
        Locale.setDefault(locale);

        Resources resources = context.getResources();

        Configuration configuration = resources.getConfiguration();
        configuration.locale = locale;
        configuration.setLayoutDirection(locale);
        resources.updateConfiguration(configuration, resources.getDisplayMetrics());

        return context;
    }
}

And below is the code I have used in my activity class

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.titleTextView)
    TextView mTitleTextView;
    @BindView(R.id.descTextView)
    TextView mDescTextView;
    @BindView(R.id.aboutTextView)
    TextView mAboutTextView;
    @BindView(R.id.toTRButton)
    Button mToTRButton;
    @BindView(R.id.toENButton)
    Button mToENButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        setTitle(getString(R.string.main_activity_toolbar_title));
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(LocaleHelper.onAttach(base));
    }

    @OnClick(R.id.toTRButton)
    public void onChangeToTRClicked() {
        updateViews("ur");
    }

    @OnClick(R.id.toENButton)
    public void onChangeToENClicked() {
        updateViews("en");
    }

    private void updateViews(String languageCode) {
        Context context = LocaleHelper.setLocale(this, languageCode);
        Resources resources = context.getResources();

        mTitleTextView.setText(resources.getString(R.string.main_activity_title));
        mDescTextView.setText(resources.getString(R.string.main_activity_desc));
        mAboutTextView.setText(resources.getString(R.string.main_activity_about));
        mToTRButton.setText(resources.getString(R.string.main_activity_to_tr_button));
        mToENButton.setText(resources.getString(R.string.main_activity_to_en_button));

        setTitle(resources.getString(R.string.main_activity_toolbar_title));
        this.recreate();
    }
}

And in my application class I have added the below code

@Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(LocaleHelper.onAttach(base, "en"));
    }

Now when I change locale from English to Urdu the language gets changed but the layout direction does not change as expected. When I again click to Urdu then layout direction changes(Second attempt). Below are the screenshots for refrence

enter image description here

App language changes but layout direction does not changed

Now layout direction is RTL but it should be LTR for English language

Please help in resolving the issue


Solution

  • The problem seems to be that it doesn't reflect layout direction changes at first update. I solved the problem by overriding the onAttachedToWindow method of Activity like below:

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            getWindow().getDecorView().setLayoutDirection(
                    "ur".equals(LocaleHelper.getLanguage(this)) ?
                    View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
        }
    }
    

    Tested on API 25 and it's working fine. Be careful though I'm not sure about any side effects for this approach at this moment. Nevertheless, I think it is what you are looking for.

    I'll also update the blog post to reflect more elegant, generic code for this.