Skip to content

Commit 63c33c0

Browse files
zoontekkkafar
andauthored
feat(Android,**unstable**): integration with react-native-edge-to-edge (#2464)
> [!note] > note by @kkafar: > Please note that I merge this as unstable behaviour, since I might want to remove the dependency & code once we land facebook/react-native#47554 in core or endup with only edge-to-edge supported by Android. ## Description This follow a discussion started [on the react-navigation repository](react-navigation/react-navigation#12217). ## Motivation The future of Android is [edge-to-edge](https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge), and to make the React Native developer experience seamless in this regard, the ecosystem needs to transition from “opaque system bars by default” to “edge-to-edge by default.” To prevent library authors from implementing their own edge-to-edge solutions—which could interfere with other libraries—and because it’s not possible to reliably detect if edge-to-edge is already enabled on Android, we have collaborated with [Expo](https://expo.dev/) to create a library that handles this functionality and is detectable using a simple helper: [`react-native-is-edge-to-edge`](https://github.com/zoontek/react-native-edge-to-edge/tree/main/react-native-is-edge-to-edge). This approach allows you to bypass certain options and props (in this case, `Screen` `statusBarTranslucent`, `navigationBarTranslucent`, `statusBarColor` and `navigationBarColor` - [setting background color is deprecated](https://developer.android.com/about/versions/15/behavior-changes-15#deprecated-apis)). All are Android only props and are now obsolete / deprecated (at least, when running in edge-to-edge mode) ## Changes - Add `react-native-is-edge-to-edge` to bypass `statusBarTranslucent`, `navigationBarTranslucent`, `statusBarColor` and `navigationBarColor` values - Add a warning about their usage when running in edge-to-edge mode. ## Test code and steps to reproduce - Install [react-native-edge-to-edge](https://github.com/zoontek/react-native-edge-to-edge) in the Example app. - Force `react-navigation` to use local version of `react-native-screens`. > [!WARNING] > For the `StatusBar` example, `react-native` `headerTopInsetEnabled` might interfere when `statusBarTranslucent` is explicitely set to `false`, but the user will receive a warning inviting him to remove the option / prop, so that's acceptable. ## More If you want to bypass some Kotlin code directly, consider this util: ```kotlin object EdgeToEdgeUtil { val ENABLED: Boolean get() = try { // we cannot detect edge-to-edge, but we can detect react-native-edge-to-edge install Class.forName("com.zoontek.rnedgetoedge.EdgeToEdgePackage") true } catch (exception: ClassNotFoundException) { false } } ``` ## Checklist - [ ] Included code example that can be used to test this change - [ ] Updated TS types - [ ] Updated documentation: <!-- For adding new props to native-stack --> - [ ] https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md - [ ] https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md - [ ] https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx - [ ] https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx - [ ] Ensured that CI passes --------- Co-authored-by: Kacper Kafara <[email protected]>
1 parent 0e418c1 commit 63c33c0

9 files changed

Lines changed: 821 additions & 47 deletions

File tree

Example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"nanoid": "^4.0.2",
2929
"react": "19.0.0",
3030
"react-native": "0.78.0-rc.5",
31+
"react-native-edge-to-edge": "^1.6.0",
3132
"react-native-gesture-handler": "2.22.0",
3233
"react-native-reanimated": "3.17.1",
3334
"react-native-restart": "^0.0.27",

Example/yarn.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3101,6 +3101,7 @@ __metadata:
31013101
react: "npm:19.0.0"
31023102
react-native: "npm:0.78.0-rc.5"
31033103
react-native-codegen: "npm:^0.71.3"
3104+
react-native-edge-to-edge: "npm:^1.6.0"
31043105
react-native-gesture-handler: "npm:2.22.0"
31053106
react-native-reanimated: "npm:3.17.1"
31063107
react-native-restart: "npm:^0.0.27"
@@ -8859,6 +8860,16 @@ __metadata:
88598860
languageName: node
88608861
linkType: hard
88618862

8863+
"react-native-edge-to-edge@npm:^1.6.0":
8864+
version: 1.6.0
8865+
resolution: "react-native-edge-to-edge@npm:1.6.0"
8866+
peerDependencies:
8867+
react: "*"
8868+
react-native: "*"
8869+
checksum: 10c0/6373cc1b447eae31689a9b62e38b15621e9273626e2324700c4c3eb58c02ce489236a4b9e3e0dc1187e062defd8316195c5b1213facd718706b79b92127a05a3
8870+
languageName: node
8871+
linkType: hard
8872+
88628873
"react-native-gesture-handler@npm:2.22.0":
88638874
version: 2.22.0
88648875
resolution: "react-native-gesture-handler@npm:2.22.0"

FabricExample/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"nanoid": "^4.0.2",
2828
"react": "19.0.0",
2929
"react-native": "0.78.0-rc.5",
30+
"react-native-edge-to-edge": "^1.6.0",
3031
"react-native-gesture-handler": "2.22.0",
3132
"react-native-reanimated": "^4.0.0-nightly-20250218-e5a0cdf69",
3233
"react-native-restart": "^0.0.27",

FabricExample/yarn.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2952,6 +2952,7 @@ __metadata:
29522952
prettier: "npm:2.8.8"
29532953
react: "npm:19.0.0"
29542954
react-native: "npm:0.78.0-rc.5"
2955+
react-native-edge-to-edge: "npm:^1.6.0"
29552956
react-native-gesture-handler: "npm:2.22.0"
29562957
react-native-reanimated: "npm:^4.0.0-nightly-20250218-e5a0cdf69"
29572958
react-native-restart: "npm:^0.0.27"
@@ -8525,6 +8526,16 @@ __metadata:
85258526
languageName: node
85268527
linkType: hard
85278528

8529+
"react-native-edge-to-edge@npm:^1.6.0":
8530+
version: 1.6.0
8531+
resolution: "react-native-edge-to-edge@npm:1.6.0"
8532+
peerDependencies:
8533+
react: "*"
8534+
react-native: "*"
8535+
checksum: 10c0/6373cc1b447eae31689a9b62e38b15621e9273626e2324700c4c3eb58c02ce489236a4b9e3e0dc1187e062defd8316195c5b1213facd718706b79b92127a05a3
8536+
languageName: node
8537+
linkType: hard
8538+
85288539
"react-native-gesture-handler@npm:2.22.0":
85298540
version: 2.22.0
85308541
resolution: "react-native-gesture-handler@npm:2.22.0"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"homepage": "https://github.com/software-mansion/react-native-screens#readme",
7070
"dependencies": {
7171
"react-freeze": "^1.0.0",
72+
"react-native-is-edge-to-edge": "^1.1.7",
7273
"warn-once": "^0.1.0"
7374
},
7475
"peerDependencies": {

src/components/Screen.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import React from 'react';
44
import { Animated, View, Platform } from 'react-native';
55

6+
import { EDGE_TO_EDGE, transformEdgeToEdgeProps } from './helpers/edge-to-edge';
7+
68
import TransitionProgressContext from '../TransitionProgressContext';
79
import DelayedFreeze from './helpers/DelayedFreeze';
810
import { ScreenProps } from '../types';
@@ -391,7 +393,12 @@ export const ScreenContext = React.createContext(InnerScreen);
391393
const Screen = React.forwardRef<View, ScreenProps>((props, ref) => {
392394
const ScreenWrapper = React.useContext(ScreenContext) || InnerScreen;
393395

394-
return <ScreenWrapper {...props} ref={ref} />;
396+
return (
397+
<ScreenWrapper
398+
{...(EDGE_TO_EDGE ? transformEdgeToEdgeProps(props) : props)}
399+
ref={ref}
400+
/>
401+
);
395402
});
396403

397404
Screen.displayName = 'Screen';

src/components/ScreenStackHeaderConfig.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
// Native components
1919
import ScreenStackHeaderConfigNativeComponent from '../fabric/ScreenStackHeaderConfigNativeComponent';
2020
import ScreenStackHeaderSubviewNativeComponent from '../fabric/ScreenStackHeaderSubviewNativeComponent';
21+
import { EDGE_TO_EDGE } from './helpers/edge-to-edge';
2122

2223
export const ScreenStackHeaderSubview: React.ComponentType<
2324
React.PropsWithChildren<ViewProps & { type?: HeaderSubviewTypes }>
@@ -30,6 +31,7 @@ export const ScreenStackHeaderConfig = React.forwardRef<
3031
<ScreenStackHeaderConfigNativeComponent
3132
{...props}
3233
ref={ref}
34+
topInsetEnabled={EDGE_TO_EDGE ? true : props.topInsetEnabled}
3335
style={styles.headerConfig}
3436
pointerEvents="box-none"
3537
/>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {
2+
controlEdgeToEdgeValues,
3+
isEdgeToEdge,
4+
} from 'react-native-is-edge-to-edge';
5+
import { ScreenProps } from '../../types';
6+
7+
export const EDGE_TO_EDGE = isEdgeToEdge();
8+
9+
export function transformEdgeToEdgeProps(props: ScreenProps): ScreenProps {
10+
const {
11+
// Filter out edge-to-edge related props
12+
statusBarColor,
13+
statusBarTranslucent,
14+
navigationBarColor,
15+
navigationBarTranslucent,
16+
...rest
17+
} = props;
18+
19+
if (__DEV__) {
20+
controlEdgeToEdgeValues({
21+
statusBarColor,
22+
statusBarTranslucent,
23+
navigationBarColor,
24+
navigationBarTranslucent,
25+
});
26+
}
27+
28+
return {
29+
...rest,
30+
statusBarTranslucent: true,
31+
navigationBarTranslucent: true,
32+
};
33+
}

0 commit comments

Comments
 (0)