Skip to content

Commit 09ef774

Browse files
zoontekfacebook-github-bot
authored andcommitted
Add edge-to-edge opt-in support (#52088)
Summary: This follows #47554 Compared to the initial proposal, I had to remove the `edgeToEdgeEnabled` property from the root `gradle.properties` and put it in the app `gradle.properties` instead (explaining the `AgpConfiguratorUtils.kt` / `GenerateEntryPointTask.kt` / `ProjectUtils.kt` / `PropertyUtils.kt` changes) This PR: - Enable edge-to-edge for `MainActivity` (when `edgeToEdgeEnabled` is set to `true`) - Disable `StatusBar` `backgroundColor` and `translucent` (when `edgeToEdgeEnabled` is set to `true`) - Enforce `statusBarTranslucent` and `navigationBarTranslucent` on `Modal` when edge-to-edge is enabled - Add an `isEdgeToEdge` constant to `DeviceInfoModule` for [`react-native-is-edge-to-edge`](https://github.com/zoontek/react-native-edge-to-edge/tree/main/react-native-is-edge-to-edge) detection ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> - [Android] [Added] - Add edge-to-edge opt-in support Pull Request resolved: #52088 Test Plan: - Update `enableEdgeToEdge` value in `packages/rn-tester/android/app/gradle.properties` - Recompile https://github.com/user-attachments/assets/4c6beb98-fa88-427c-b62d-a42ffe5330f0 Rollback Plan: Reviewed By: cortinico Differential Revision: D76834213 Pulled By: alanleedev fbshipit-source-id: c39b2cff1a5e94e31306e3b35651aa2de83d2fe6
1 parent 78c9671 commit 09ef774

File tree

17 files changed

+200
-54
lines changed

17 files changed

+200
-54
lines changed

packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ abstract class GenerateEntryPointTask : DefaultTask() {
7070
7171
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
7272
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
73+
import com.facebook.react.views.view.WindowUtilKt;
7374
import com.facebook.react.soloader.OpenSourceMergedSoMapping;
7475
import com.facebook.soloader.SoLoader;
7576
@@ -93,6 +94,10 @@ abstract class GenerateEntryPointTask : DefaultTask() {
9394
if ({{packageName}}.BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
9495
DefaultNewArchitectureEntryPoint.load();
9596
}
97+
98+
if ({{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) {
99+
WindowUtilKt.setEdgeToEdgeFeatureFlagOn();
100+
}
96101
}
97102
}
98103
"""

packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension
1111
import com.android.build.api.variant.LibraryAndroidComponentsExtension
1212
import com.android.build.gradle.LibraryExtension
1313
import com.facebook.react.ReactExtension
14+
import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled
1415
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
1516
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
1617
import java.io.File
@@ -39,6 +40,8 @@ internal object AgpConfiguratorUtils {
3940
project.isNewArchEnabled(extension).toString())
4041
ext.defaultConfig.buildConfigField(
4142
"boolean", "IS_HERMES_ENABLED", project.isHermesEnabled.toString())
43+
ext.defaultConfig.buildConfigField(
44+
"boolean", "IS_EDGE_TO_EDGE_ENABLED", project.isEdgeToEdgeEnabled.toString())
4245
}
4346
}
4447
project.pluginManager.withPlugin("com.android.application", action)

packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import com.facebook.react.ReactExtension
1111
import com.facebook.react.model.ModelPackageJson
1212
import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat
1313
import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat
14+
import com.facebook.react.utils.PropertyUtils.EDGE_TO_EDGE_ENABLED
1415
import com.facebook.react.utils.PropertyUtils.HERMES_ENABLED
1516
import com.facebook.react.utils.PropertyUtils.NEW_ARCH_ENABLED
1617
import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES
18+
import com.facebook.react.utils.PropertyUtils.SCOPED_EDGE_TO_EDGE_ENABLED
1719
import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED
1820
import com.facebook.react.utils.PropertyUtils.SCOPED_NEW_ARCH_ENABLED
1921
import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES
@@ -59,6 +61,13 @@ internal object ProjectUtils {
5961
HERMES_FALLBACK
6062
}
6163

64+
internal val Project.isEdgeToEdgeEnabled: Boolean
65+
get() =
66+
(project.hasProperty(EDGE_TO_EDGE_ENABLED) &&
67+
project.property(EDGE_TO_EDGE_ENABLED).toString().toBoolean()) ||
68+
(project.hasProperty(SCOPED_EDGE_TO_EDGE_ENABLED) &&
69+
project.property(SCOPED_EDGE_TO_EDGE_ENABLED).toString().toBoolean())
70+
6271
internal val Project.useThirdPartyJSC: Boolean
6372
get() =
6473
(project.hasProperty(USE_THIRD_PARTY_JSC) &&

packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ object PropertyUtils {
1414
const val NEW_ARCH_ENABLED = "newArchEnabled"
1515
const val SCOPED_NEW_ARCH_ENABLED = "react.newArchEnabled"
1616

17-
/** Public property that toggles the New Architecture */
17+
/** Public property that toggles Hermes */
1818
const val HERMES_ENABLED = "hermesEnabled"
1919
const val SCOPED_HERMES_ENABLED = "react.hermesEnabled"
2020

21+
/** Public property that toggles edge-to-edge */
22+
const val EDGE_TO_EDGE_ENABLED = "edgeToEdgeEnabled"
23+
const val SCOPED_EDGE_TO_EDGE_ENABLED = "react.edgeToEdgeEnabled"
24+
2125
/** Public property that excludes jsctooling from core */
2226
const val USE_THIRD_PARTY_JSC = "useThirdPartyJSC"
2327
const val SCOPED_USE_THIRD_PARTY_JSC = "react.useThirdPartyJSC"

packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class GenerateEntryPointTaskTest {
5555
5656
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
5757
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
58+
import com.facebook.react.views.view.WindowUtilKt;
5859
import com.facebook.react.soloader.OpenSourceMergedSoMapping;
5960
import com.facebook.soloader.SoLoader;
6061
@@ -78,6 +79,10 @@ class GenerateEntryPointTaskTest {
7879
if (com.facebook.react.BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
7980
DefaultNewArchitectureEntryPoint.load();
8081
}
82+
83+
if (com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) {
84+
WindowUtilKt.setEdgeToEdgeFeatureFlagOn();
85+
}
8186
}
8287
}
8388
"""

packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.facebook.react.model.ModelCodegenConfig
1212
import com.facebook.react.model.ModelPackageJson
1313
import com.facebook.react.tests.createProject
1414
import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures
15+
import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled
1516
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
1617
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
1718
import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
@@ -98,7 +99,7 @@ class ProjectUtilsTest {
9899
}
99100

100101
@Test
101-
fun isNewArchEnabled_withDisabledViaProperty_returnsFalse() {
102+
fun isHermesEnabled_withDisabledViaProperty_returnsFalse() {
102103
val project = createProject()
103104
project.extensions.extraProperties.set("hermesEnabled", "false")
104105
assertThat(project.isHermesEnabled).isFalse()
@@ -150,6 +151,32 @@ class ProjectUtilsTest {
150151
assertThat(project.isHermesEnabled).isTrue()
151152
}
152153

154+
@Test
155+
fun isEdgeToEdgeEnabled_returnsFalseByDefault() {
156+
assertThat(createProject().isEdgeToEdgeEnabled).isFalse()
157+
}
158+
159+
@Test
160+
fun isEdgeToEdgeEnabled_withDisabledViaProperty_returnsFalse() {
161+
val project = createProject()
162+
project.extensions.extraProperties.set("edgeToEdgeEnabled", "false")
163+
assertThat(project.isEdgeToEdgeEnabled).isFalse()
164+
}
165+
166+
@Test
167+
fun isEdgeToEdgeEnabled_withEnabledViaProperty_returnsTrue() {
168+
val project = createProject()
169+
project.extensions.extraProperties.set("edgeToEdgeEnabled", "true")
170+
assertThat(project.isEdgeToEdgeEnabled).isTrue()
171+
}
172+
173+
@Test
174+
fun isEdgeToEdgeEnabled_withInvalidViaProperty_returnsFalse() {
175+
val project = createProject()
176+
project.extensions.extraProperties.set("edgeToEdgeEnabled", "¯\\_(ツ)_/¯")
177+
assertThat(project.isEdgeToEdgeEnabled).isFalse()
178+
}
179+
153180
@Test
154181
fun needsCodegenFromPackageJson_withCodegenConfigInPackageJson_returnsTrue() {
155182
val project = createProject()

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6673,3 +6673,8 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react
66736673
public final class com/facebook/react/views/view/ReactViewManager$Companion {
66746674
}
66756675

6676+
public final class com/facebook/react/views/view/WindowUtilKt {
6677+
public static final fun isEdgeToEdgeFeatureFlagOn ()Z
6678+
public static final fun setEdgeToEdgeFeatureFlagOn ()V
6679+
}
6680+

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.os.Build;
1616
import android.os.Bundle;
1717
import android.view.KeyEvent;
18+
import android.view.Window;
1819
import androidx.annotation.Nullable;
1920
import com.facebook.infer.annotation.Assertions;
2021
import com.facebook.react.bridge.Callback;
@@ -23,6 +24,7 @@
2324
import com.facebook.react.interfaces.fabric.ReactSurface;
2425
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags;
2526
import com.facebook.react.modules.core.PermissionListener;
27+
import com.facebook.react.views.view.WindowUtilKt;
2628
import com.facebook.systrace.Systrace;
2729

2830
/**
@@ -57,7 +59,7 @@ public ReactActivityDelegate(
5759

5860
/**
5961
* Public API to populate the launch options that will be passed to React. Here you can customize
60-
* the values that will be passed as `initialProperties` to the Renderer.
62+
* the values that will be passed as 'initialProperties' to the Renderer.
6163
*
6264
* @return Either null or a key-value map as a Bundle
6365
*/
@@ -121,8 +123,16 @@ public void onCreate(Bundle savedInstanceState) {
121123
() -> {
122124
String mainComponentName = getMainComponentName();
123125
final Bundle launchOptions = composeLaunchOptions();
124-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isWideColorGamutEnabled()) {
125-
mActivity.getWindow().setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
126+
if (mActivity != null) {
127+
Window window = mActivity.getWindow();
128+
if (window != null) {
129+
if (WindowUtilKt.isEdgeToEdgeFeatureFlagOn()) {
130+
WindowUtilKt.enableEdgeToEdge(window);
131+
}
132+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isWideColorGamutEnabled()) {
133+
window.setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
134+
}
135+
}
126136
}
127137
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
128138
mReactDelegate =

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.facebook.react.bridge.ReadableMap
1616
import com.facebook.react.module.annotations.ReactModule
1717
import com.facebook.react.uimanager.DisplayMetricsHolder.getDisplayMetricsWritableMap
1818
import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized
19+
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
1920

2021
/** Module that exposes Android Constants to JS. */
2122
@ReactModule(name = NativeDeviceInfoSpec.NAME)
@@ -34,7 +35,11 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
3435

3536
// Cache the initial dimensions for later comparison in emitUpdateDimensionsEvent
3637
previousDisplayMetrics = displayMetrics.copy()
37-
return mapOf("Dimensions" to displayMetrics.toHashMap())
38+
39+
return mapOf(
40+
"Dimensions" to displayMetrics.toHashMap(),
41+
"isEdgeToEdge" to isEdgeToEdgeFeatureFlagOn,
42+
)
3843
}
3944

4045
override fun onHostResume() {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.facebook.react.common.ReactConstants
2323
import com.facebook.react.module.annotations.ReactModule
2424
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
2525
import com.facebook.react.uimanager.PixelUtil
26+
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
2627
import com.facebook.react.views.view.setStatusBarTranslucency
2728
import com.facebook.react.views.view.setStatusBarVisibility
2829

@@ -54,6 +55,12 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
5455
"StatusBarModule: Ignored status bar change, current activity is null.")
5556
return
5657
}
58+
if (isEdgeToEdgeFeatureFlagOn) {
59+
FLog.w(
60+
ReactConstants.TAG,
61+
"StatusBarModule: Ignored status bar change, current activity is edge-to-edge.")
62+
return
63+
}
5764
UiThreadUtil.runOnUiThread(
5865
object : GuardedRunnable(reactApplicationContext) {
5966
override fun runGuarded() {
@@ -82,6 +89,12 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
8289
"StatusBarModule: Ignored status bar change, current activity is null.")
8390
return
8491
}
92+
if (isEdgeToEdgeFeatureFlagOn) {
93+
FLog.w(
94+
ReactConstants.TAG,
95+
"StatusBarModule: Ignored status bar change, current activity is edge-to-edge.")
96+
return
97+
}
8598
UiThreadUtil.runOnUiThread(
8699
object : GuardedRunnable(reactApplicationContext) {
87100
override fun runGuarded() {

0 commit comments

Comments
 (0)