Skip to content

Commit

Permalink
Set and require android:supportsRtl="true" for RTL layout
Browse files Browse the repository at this point in the history
Summary:
Android originated without RTL support. When RTL support was added, Applications needed to set `android:supportsRtl="true"` in their manifest, to allow Android to do RTL specific layout and drawing. This became the default for new projects created by Android Studio at some point.

React Native was not setting this in template, which means apps created from it do not do any of Android's RTL layout, text alignment, or drawing (e.g. in D3652980 8 years ago, a native drawer component came from the wrong side of the screen). RN would still layout the app using Yoga in RTL if in RTL locale though.

This change sets `android:supportsRtl` in template matching default new Android projects, and to avoid mismatched states in the future, will only tell I18NManager that RTL is allowed if `android:supportsRtl` is also set. This is breaking, since existing apps may not get Yoga RTL support unless telling Android that the application should support RTL layout.

Changelog:
[Android][Breaking] - Set and require `android:supportsRtl="true"` for RTL layout

Differential Revision: D57248205
  • Loading branch information
NickGerleman authored and facebook-github-bot committed May 11, 2024
1 parent 3f17c8b commit aced5b1
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,26 @@

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import androidx.core.text.TextUtilsCompat;
import androidx.core.view.ViewCompat;
import com.facebook.common.logging.FLog;
import java.lang.reflect.Method;
import java.util.Locale;

public class I18nUtil {
private static I18nUtil sharedI18nUtilInstance = null;

private static final String TAG = "I18nUtil";
private static final String SHARED_PREFS_NAME = "com.facebook.react.modules.i18nmanager.I18nUtil";
private static final String KEY_FOR_PREFS_ALLOWRTL = "RCTI18nUtil_allowRTL";
private static final String KEY_FOR_PREFS_FORCERTL = "RCTI18nUtil_forceRTL";
private static final String KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES =
"RCTI18nUtil_makeRTLFlipLeftAndRightStyles";

private boolean mHasCheckedRtlSupport = false;
private boolean mHasRtlSupport = true;

private I18nUtil() {
// Exists only to defeat instantiation.
}
Expand All @@ -42,19 +49,45 @@ public static I18nUtil getInstance() {
* </ul>
*/
public boolean isRTL(Context context) {
if (!applicationHasRtlSupport(context)) {
return false;
}

if (isRTLForced(context)) {
return true;
}

return isRTLAllowed(context) && isDevicePreferredLanguageRTL();
}

/**
* Android relies on the presence of `android:supportsRtl="true"` being set in order to resolve
* RTL as a layout direction for native Android views. RTL in React Native relies on this being
* set.
*/
private boolean applicationHasRtlSupport(Context context) {
if (!mHasCheckedRtlSupport) {
mHasCheckedRtlSupport = true;
ApplicationInfo applicationInfo = context.getApplicationInfo();
try {
Method hasRtlSupport = ApplicationInfo.class.getMethod("hasRtlSupport");
mHasRtlSupport = (boolean) hasRtlSupport.invoke(applicationInfo);
} catch (ReflectiveOperationException e) {
FLog.w(TAG, "Failed to check if application has RTL support");
}
}
return mHasRtlSupport;
}

/**
* Should be used very early during app start up Before the bridge is initialized
*
* @return whether the app allows RTL layout, default is true
*/
private boolean isRTLAllowed(Context context) {
return isPrefSet(context, KEY_FOR_PREFS_ALLOWRTL, true);
// We should only claim to allow RTL if `android:supportsRtl="true"` is set, otherwise the
// platform apart from Yoga ignores layout direction.
return applicationHasRtlSupport(context) && isPrefSet(context, KEY_FOR_PREFS_ALLOWRTL, true);
}

public void allowRTL(Context context, boolean allowRTL) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
android:supportsRtl="true">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
Expand Down
3 changes: 2 additions & 1 deletion packages/rn-tester/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
android:supportsRtl="true">
<activity
android:name=".RNTesterActivity"
android:label="@string/app_name"
Expand Down

0 comments on commit aced5b1

Please sign in to comment.