Skip to content

Fix Dimensions window values on Android < 15 #47554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

zoontek
Copy link
Contributor

@zoontek zoontek commented Nov 11, 2024

Summary:

This PR (initially created for edge-to-edge opt-in support, rebased multiple times) fixes the Dimensions API window values on Android < 15, when edge-to-edge is enabled.

Currently the window height doesn't include the status and navigation bar heights (but it does on Android >= 15):

Screenshot 2025-06-27 at 16 23 02

Using WindowMetricsCalculator from AndroidX:

Screenshot 2025-06-27 at 16 34 01

Fixes #47080

Changelog:

[Android] [Fixed] Fix Dimensions window values on Android < 15 when edge-to-edge is enabled

Test Plan:

Run the example app on an Android < 15 device.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 11, 2024
@janicduplessis
Copy link
Contributor

Why would StatusBar backgroundColor prop have no effect in edge to edge? I have cases where I use a semi transparent background in edge to edge mode.

@zoontek
Copy link
Contributor Author

zoontek commented Nov 12, 2024

@janicduplessis Setting status bar color (window.setStatusBarColor) is deprecated and has no effect on Android 15+ (when targetting SDK35) - source.

setStatusBarColor and R.attr#statusBarColor are deprecated and have no effect on Android 15.

Deprecated APIs

  The following APIs are deprecated and disabled:
  …
  Window#setStatusBarColor

Instead, you can still put an empty View on the top of their screen, with a backgroundColor and using top inset as height. Bonus: it will works on every platform, not just Android.

@alanleedev
Copy link
Contributor

@zoontek Would there be a way to avoid adding isEdgeToEdgeEnabled to ReactHost?

@zoontek
Copy link
Contributor Author

zoontek commented Nov 12, 2024

@alanleedev Would you be OK in using BuildConfig.IS_EDGE_TO_EDGE_ENABLED directly? Or maybe having a simple fun isEdgeToEdgeEnabled(): Boolean = BuildConfig.IS_EDGE_TO_EDGE_ENABLED somewhere?

@grahammendick
Copy link
Contributor

To enable edge-to-edge in SDK < 35 Android’s recommendation is to call enableEdgeToEdge on ComponentActivity. Why isn’t that the approach taken here? And shouldn’t Appearance.isEdgeToEdge return true if the Android recommended approach is used (even if that isn’t the approach used by React Native)?

@zoontek
Copy link
Contributor Author

zoontek commented Nov 12, 2024

@grahammendick Because Window.enableEdgeToEdge is also used for Modal, which extends Dialog. The current code is based on the AndroidX implementation, just not tied to ComponentActivity.

@grahammendick
Copy link
Contributor

@zoontek Most people that use React Native don't use the React Native Modal component. So they can turn on edge-to-edge by calling enableEdgeToEdge. Wouldn’t it be odd if Appearance.isEdgeToEdge returned false in that case? Maybe it would be better to work out if edge-to-edge is enabled, for example, by listening for windows insets instead? Then React Native can make its Modal edge-to-edge if insets are applied?

@zoontek
Copy link
Contributor Author

zoontek commented Nov 13, 2024

@grahammendick Android doesn't provide a way to properly detect if edge-to-edge is enabled, so even if let's say, the user call ComponentActivity.enableEdgeToEdge on their side (not by using enabledEdgeToEdge=true), Appearance.isEdgeToEdge will be false too.

Add the facts that:

  • it requires adding androidx.activity:activity to RN
  • it doesn't easily work with Modal, or any Dialog
  • the whole logic is ~30 lines:
public fun Window.enableEdgeToEdge() {
  val isDarkMode = ContextUtils.isDarkMode(context)

  WindowCompat.setDecorFitsSystemWindows(this, false)

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    isStatusBarContrastEnforced = false
    isNavigationBarContrastEnforced = true
  }

  statusBarColor = Color.TRANSPARENT

  navigationBarColor =
    when {
      Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Color.TRANSPARENT
      Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isDarkMode -> Color.argb(0xe6, 0xFF, 0xFF, 0xFF)
      else -> Color.argb(0x80, 0x1b, 0x1b, 0x1b)
    }

  WindowInsetsControllerCompat(this, this.decorView).run {
    isAppearanceLightNavigationBars = !isDarkMode
  }

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    attributes.layoutInDisplayCutoutMode =
      when {
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
        else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
      }
  }
}

And it's hard to justify going that way.

@grahammendick
Copy link
Contributor

Android doesn't provide a way to properly detect if edge-to-edge is enabled

React Native can add a OnApplyWindowInsets listener to the root view. If any top/left/right/bottom inset is > 0 then edge-to-edge is enabled. That should work no matter how edge-to-edge is enabled

@zoontek
Copy link
Contributor Author

zoontek commented Nov 13, 2024

@grahammendick Even with that, you still need to have this function in RN to apply edge-to-edge on Modal. That's why the extra dependency isn't worth it.

Regarding using OnApplyWindowInsets for Appearance.isEdgeToEdge, this is an idea, but this could result in a different value than the BuildConfig.IS_EDGE_TO_EDGE_ENABLED one.
Let's suppose user are enabling edge-to-edge without using the built-in solution: libraries relying on BuildConfig.IS_EDGE_TO_EDGE_ENABLED on the native side will interferes as it will return false. Meanwhile, on the JS side, Apperance.isEdgeToEdge() will be true.

@cipolleschi
Copy link
Contributor

I'm not really the right person to review this as I don't have that extensive knowledge on Android.
I just want to mention that @cortinico is on PTO for a couple of weeks, and he probably have some opinions on this proposal, so I'd kindly ask for a bit of patience and to wait for him to come back. 🙏

private fun Window.statusBarShow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
private fun Window.statusBarShow(isEdgeToEdge: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !isEdgeToEdge) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always was hesitant to ask, but now I feel like it's a right time 🙂

From what I remember StatusBar management uses some deprecated methods for managing its properties. As a result it may break some animations driven by WindowInsetsAnimationCompat.Callback.

To overcome this problem I had to create own module based on WindowInsetsControllerCompat class and monkey-patch JS module afterward.

This is directly not related to this PR, but I'd like to discuss a possibility to rewrite StatusBar management to a new/modern API 🙌 (whether FB team is open to it and if not then what blocks/prevents that).

Copy link
Contributor

@alanleedev alanleedev Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kirillzyusko We can discuss about it if we have bit more details on the change you wan to make. There is also react-native-edge-to-edge SystemBars. So may need to check if it would be better to update that instead.

@kirillzyusko
Copy link
Contributor

Wouldn’t it be odd if Appearance.isEdgeToEdge returned false in that case? Maybe it would be better to work out if edge-to-edge is enabled, for example, by listening for windows insets instead? Then React Native can make its Modal edge-to-edge if insets are applied?

I also agree with @zoontek here. Theoretically we can listen to root view insets and based on that decide whether we are in edge-to-edge mode or not. But in my opinion it will add more code complexity and at some point of time there will appear a situations, when something not working etc.

The case that you described it a simple misconfiguration between two modules (user-defined code and RN). and such misconfiguration happens relatively frequently now - that's why @zoontek created react-native-edge-to-edge and react-native-is-edge-to-edge and opened PRs to many repos to adopt edge-to-edge in RN ecosystem.

I think the best way here would be to write a blog post explaining for people, that if they used edge-to-edge before (and maybe list third party dependencies that are enabling this mode by default), then they need to specify edgeToEdge=true in gradle.properties. It's always better to have a single source of truth (value inside gradle.properties) in a manageable/configurable way (in my opinion) 🙂

@zoontek zoontek force-pushed the proposal-edge-to-edge branch from e101925 to 90ad847 Compare December 3, 2024 10:10
github-merge-queue bot pushed a commit to software-mansion/react-native-reanimated that referenced this pull request Feb 24, 2025
…isNavigationBarTranslucentAndroid (#6732)

## Summary

Similar to [the
PR](software-mansion/react-native-screens#2464)
I opened on the `react-native-screens` repository (I highly recommend to
read the discussion there to understand the motivation behind this),
this PR detects if the user enabled edge-to-edge and act accordingly:
`useAnimatedKeyboard` are ignored, set to `true` automatically. If those
are set, a warning is logged:

> `isStatusBarTranslucentAndroid` and
`isNavigationBarTranslucentAndroid` values are ignored when `using
react-native-edge-to-edge`

It at some point [this
proposal](facebook/react-native#47554) lands in
core, `react-native-is-edge-to-edge` will be updated to support both the
library and the core edge-to-edge flag, making the transition seamless
for the users.

## Test plan

- Install
[react-native-edge-to-edge](https://github.com/zoontek/react-native-edge-to-edge)
in the example app.
- Don't set `isStatusBarTranslucentAndroid` /
`isNavigationBarTranslucentAndroid`, or set them to something else than
`true`

---------

Co-authored-by: Bartłomiej Błoniarz <[email protected]>
tjzel pushed a commit to software-mansion/react-native-reanimated that referenced this pull request Feb 24, 2025
…isNavigationBarTranslucentAndroid (#6732)

## Summary

Similar to [the
PR](software-mansion/react-native-screens#2464)
I opened on the `react-native-screens` repository (I highly recommend to
read the discussion there to understand the motivation behind this),
this PR detects if the user enabled edge-to-edge and act accordingly:
`useAnimatedKeyboard` are ignored, set to `true` automatically. If those
are set, a warning is logged:

> `isStatusBarTranslucentAndroid` and
`isNavigationBarTranslucentAndroid` values are ignored when `using
react-native-edge-to-edge`

It at some point [this
proposal](facebook/react-native#47554) lands in
core, `react-native-is-edge-to-edge` will be updated to support both the
library and the core edge-to-edge flag, making the transition seamless
for the users.

## Test plan

- Install
[react-native-edge-to-edge](https://github.com/zoontek/react-native-edge-to-edge)
in the example app.
- Don't set `isStatusBarTranslucentAndroid` /
`isNavigationBarTranslucentAndroid`, or set them to something else than
`true`

---------

Co-authored-by: Bartłomiej Błoniarz <[email protected]>
tjzel pushed a commit to software-mansion/react-native-reanimated that referenced this pull request Feb 24, 2025
…isNavigationBarTranslucentAndroid (#6732)

## Summary

Similar to [the
PR](software-mansion/react-native-screens#2464)
I opened on the `react-native-screens` repository (I highly recommend to
read the discussion there to understand the motivation behind this),
this PR detects if the user enabled edge-to-edge and act accordingly:
`useAnimatedKeyboard` are ignored, set to `true` automatically. If those
are set, a warning is logged:

> `isStatusBarTranslucentAndroid` and
`isNavigationBarTranslucentAndroid` values are ignored when `using
react-native-edge-to-edge`

It at some point [this
proposal](facebook/react-native#47554) lands in
core, `react-native-is-edge-to-edge` will be updated to support both the
library and the core edge-to-edge flag, making the transition seamless
for the users.

## Test plan

- Install
[react-native-edge-to-edge](https://github.com/zoontek/react-native-edge-to-edge)
in the example app.
- Don't set `isStatusBarTranslucentAndroid` /
`isNavigationBarTranslucentAndroid`, or set them to something else than
`true`

---------

Co-authored-by: Bartłomiej Błoniarz <[email protected]>
@kkafar
Copy link
Contributor

kkafar commented Apr 18, 2025

Hey, I want to rekindle the discussion here. Is progress here blocked & we settled with putting source of truth for edge-to-edge in third-party-library or is it still planned to land this proposal eventually in core?

@facebook-github-bot
Copy link
Contributor

This pull request has been reverted by b4dcc98.

@cortinico
Copy link
Contributor

@zoontek we had to revert this because of integration issues

@zoontek
Copy link
Contributor Author

zoontek commented Jul 4, 2025

@cortinico Is there something that could be done?

@cortinico
Copy link
Contributor

@cortinico Is there something that could be done?

We're looking inot it. We'll keep you in the loop if there is anything needed on your end 👍

alanleedev pushed a commit to alanleedev/react-native that referenced this pull request Jul 8, 2025
Summary:
This PR (initially created for edge-to-edge opt-in support, rebased multiple times) fixes the `Dimensions` API `window` values on Android < 15, when edge-to-edge is enabled.

Currently the window height doesn't include the status and navigation bar heights (but it does on Android >= 15):

<img width="300" alt="Screenshot 2025-06-27 at 16 23 02" src="https://github.com/user-attachments/assets/c7d11334-9298-4f7f-a75c-590df8cc2d8a" />

Using `WindowMetricsCalculator` from AndroidX:

<img width="300" alt="Screenshot 2025-06-27 at 16 34 01" src="https://github.com/user-attachments/assets/7a4e3dc7-a83b-421b-8f6d-fd1344f5fe81" />

Fixes facebook#47080

## Changelog:

[Android] [Fixed] Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled


Test Plan:
Run the example app on an Android < 15 device.

Rollback Plan:

Differential Revision: D77906644

Pulled By: alanleedev
motiz88 pushed a commit that referenced this pull request Jul 14, 2025
Summary:
This PR (initially created for edge-to-edge opt-in support, rebased multiple times) fixes the `Dimensions` API `window` values on Android < 15, when edge-to-edge is enabled.

Currently the window height doesn't include the status and navigation bar heights (but it does on Android >= 15):

<img width="300" alt="Screenshot 2025-06-27 at 16 23 02" src="https://github.com/user-attachments/assets/c7d11334-9298-4f7f-a75c-590df8cc2d8a" />

Using `WindowMetricsCalculator` from AndroidX:

<img width="300" alt="Screenshot 2025-06-27 at 16 34 01" src="https://github.com/user-attachments/assets/7a4e3dc7-a83b-421b-8f6d-fd1344f5fe81" />

Fixes #47080

## Changelog:

[Android] [Fixed] Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled

Pull Request resolved: #47554

Test Plan:
Run the example app on an Android < 15 device.

Rollback Plan:

Reviewed By: cortinico

Differential Revision: D77547628

Pulled By: alanleedev

fbshipit-source-id: 9d841f642d5b7ef3294dfbf3868137087a672ad6
cortinico added a commit that referenced this pull request Jul 14, 2025
@cortinico
Copy link
Contributor

Hey @zoontek
I had to revert this change on the 0.81 release branch because it's making RNTester on Android 7.0 instacrash with:

07-14 14:38:13.572  3999  4019 E unknown:ReactNative: Exception in native call
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: java.lang.IllegalArgumentException: Context com.facebook.react.runtime.BridgelessReactContext@97f69a1 is not a UiContext
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at androidx.window.layout.util.ContextCompatHelper.unwrapUiContext$window_release(ContextCompatHelper.kt:52)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at androidx.window.layout.util.WindowMetricsCompatHelperBaseImpl.currentWindowMetrics(WindowMetricsCompatHelper.kt:84)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at androidx.window.layout.WindowMetricsCalculatorCompat.computeCurrentWindowMetrics(WindowMetricsCalculatorCompat.kt:39)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetrics(DisplayMetricsHolder.kt:74)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(DisplayMetricsHolder.kt:61)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.ReactInstance.<init>(ReactInstance.kt:243)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.ReactHostImpl.getOrCreateReactInstanceTask$lambda$41$lambda$38(ReactHostImpl.kt:919)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.ReactHostImpl.$r8$lambda$LQY77bFECGrnkUqd7Ku55sJ721A(ReactHostImpl.kt)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.ReactHostImpl$$ExternalSyntheticLambda22.then(D8$$SyntheticClass:0)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeImmediately$lambda$3(Task.kt:356)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.$r8$lambda$b7MTC0ufkn_2TjF4BGcNqGR9qK0(Task.kt)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Executors$ImmediateExecutor.execute(Executors.kt:41)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeImmediately(Task.kt:354)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.access$completeImmediately(Task.kt:250)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task.continueWith(Task.kt:132)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task.continueWith$default(Task.kt:117)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task.onSuccess$lambda$12(Task.kt:174)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task.$r8$lambda$l0wwo5AvbHxlwfI1R7CH_codxoo(Task.kt)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$$ExternalSyntheticLambda2.then(D8$$SyntheticClass:0)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeAfterTask$lambda$5(Task.kt:390)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.$r8$lambda$IPpysnehFwANEiW061YzapkMdoE(Task.kt)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion$$ExternalSyntheticLambda4.run(D8$$SyntheticClass:0)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at java.lang.Thread.run(Thread.java:761)

Could you look into it?

cc @alanleedev for visibility

@zoontek
Copy link
Contributor Author

zoontek commented Jul 14, 2025

Hey @zoontek I had to revert this change on the 0.81 release branch because it's making RNTester on Android 7.0 instacrash with:

07-14 14:38:13.572  3999  4019 E unknown:ReactNative: Exception in native call
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: java.lang.IllegalArgumentException: Context com.facebook.react.runtime.BridgelessReactContext@97f69a1 is not a UiContext
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at androidx.window.layout.util.ContextCompatHelper.unwrapUiContext$window_release(ContextCompatHelper.kt:52)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at androidx.window.layout.util.WindowMetricsCompatHelperBaseImpl.currentWindowMetrics(WindowMetricsCompatHelper.kt:84)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at androidx.window.layout.WindowMetricsCalculatorCompat.computeCurrentWindowMetrics(WindowMetricsCalculatorCompat.kt:39)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetrics(DisplayMetricsHolder.kt:74)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(DisplayMetricsHolder.kt:61)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.ReactInstance.<init>(ReactInstance.kt:243)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.ReactHostImpl.getOrCreateReactInstanceTask$lambda$41$lambda$38(ReactHostImpl.kt:919)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.ReactHostImpl.$r8$lambda$LQY77bFECGrnkUqd7Ku55sJ721A(ReactHostImpl.kt)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.ReactHostImpl$$ExternalSyntheticLambda22.then(D8$$SyntheticClass:0)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeImmediately$lambda$3(Task.kt:356)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.$r8$lambda$b7MTC0ufkn_2TjF4BGcNqGR9qK0(Task.kt)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Executors$ImmediateExecutor.execute(Executors.kt:41)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeImmediately(Task.kt:354)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.access$completeImmediately(Task.kt:250)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task.continueWith(Task.kt:132)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task.continueWith$default(Task.kt:117)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task.onSuccess$lambda$12(Task.kt:174)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task.$r8$lambda$l0wwo5AvbHxlwfI1R7CH_codxoo(Task.kt)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$$ExternalSyntheticLambda2.then(D8$$SyntheticClass:0)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeAfterTask$lambda$5(Task.kt:390)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion.$r8$lambda$IPpysnehFwANEiW061YzapkMdoE(Task.kt)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at com.facebook.react.runtime.internal.bolts.Task$Companion$$ExternalSyntheticLambda4.run(D8$$SyntheticClass:0)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
07-14 14:38:13.572  3999  4019 E unknown:ReactNative: 	at java.lang.Thread.run(Thread.java:761)

Could you look into it?

cc @alanleedev for visibility

@cipolleschi @alanleedev That's because the method is called with a non-UI context and this doesn't work on Android < R.

To fix this, in this PR, replace this line:

DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());

with:

DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext());

and this line (to prevent crash on orientation change):

DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());

with:

DisplayMetricsHolder.initDisplayMetrics(getContext());
UiContext.fix.mp4

@zoontek
Copy link
Contributor Author

zoontek commented Jul 14, 2025

Also tested with edgeToEdgeEnabled=false, this doesn't change the existing behavior at all:

Screenshot 2025-07-14 at 17 32 37

alanleedev pushed a commit to alanleedev/react-native that referenced this pull request Jul 15, 2025
Summary:
Pull Request resolved: facebook#52481

This PR (initially created for edge-to-edge opt-in support, rebased multiple times) fixes the `Dimensions` API `window` values on Android < 15, when edge-to-edge is enabled.

Currently the window height doesn't include the status and navigation bar heights (but it does on Android >= 15):

<img width="300" alt="Screenshot 2025-06-27 at 16 23 02" src="https://github.com/user-attachments/assets/c7d11334-9298-4f7f-a75c-590df8cc2d8a" />

Using `WindowMetricsCalculator` from AndroidX:

<img width="300" alt="Screenshot 2025-06-27 at 16 34 01" src="https://github.com/user-attachments/assets/7a4e3dc7-a83b-421b-8f6d-fd1344f5fe81" />

Fixes facebook#47080

## Changelog:

[Android] [Fixed] Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled

Pull Request resolved: facebook#47554

Test Plan:
Run the example app on an Android < 15 device.

Rollback Plan:

Reviewed By: cortinico

Differential Revision: D77906644
@alanleedev
Copy link
Contributor

alanleedev commented Jul 15, 2025

@zoontek
Seem like the crash log shows it being called from ReactInstance and not ReactRootView. Were you able to verify the crash on Android 7 before the fix?

07-14 14:38:13.572 3999 4019 E unknown:ReactNative: at com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(DisplayMetricsHolder.kt:61)
07-14 14:38:13.572 3999 4019 E unknown:ReactNative: at com.facebook.react.runtime.ReactInstance.(ReactInstance.kt:243)

https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt#L243

@zoontek
Copy link
Contributor Author

zoontek commented Jul 15, 2025

@alanleedev Yes, tried it on an Android 7 emulator (check the video). Isn't the ReactInstance what is returned by getContext().getApplicationContext(). Does the issue still happen in E2E?

@alanleedev
Copy link
Contributor

alanleedev commented Jul 16, 2025

@zoontek I tried on Android 7 emulator without the change and it didn't crash for me.
@cortinico Is there a way to run the test that crashed based on PR #52614 ? Or any suggestions to make sure this fixes the issue?

@zoontek
Copy link
Contributor Author

zoontek commented Jul 16, 2025

@alanleedev When I tried it with getContext().getApplicationContext(), I successfully reproduce the crash: first at app start, and once the first occurence (line 868) has been replaced, at app orientation change (line 991).

By passing UI context (getContext().getApplicationContext() -> getContext()), no more crashes on my side.

@alanleedev
Copy link
Contributor

@zoontek That is weird, it does not repro for me on my emulator running on ARM based Mac. At least, I don't see any issues with the code fix. I guess we can try to merge and see if we run into any issues in the release branch.

facebook-github-bot pushed a commit that referenced this pull request Jul 16, 2025
Summary:
Pull Request resolved: #52481

This PR (initially created for edge-to-edge opt-in support, rebased multiple times) fixes the `Dimensions` API `window` values on Android < 15, when edge-to-edge is enabled.

Currently the window height doesn't include the status and navigation bar heights (but it does on Android >= 15):

<img width="300" alt="Screenshot 2025-06-27 at 16 23 02" src="https://github.com/user-attachments/assets/c7d11334-9298-4f7f-a75c-590df8cc2d8a" />

Using `WindowMetricsCalculator` from AndroidX:

<img width="300" alt="Screenshot 2025-06-27 at 16 34 01" src="https://github.com/user-attachments/assets/7a4e3dc7-a83b-421b-8f6d-fd1344f5fe81" />

Fixes #47080

## Changelog:

[Android] [Fixed] Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled

Pull Request resolved: #47554

Test Plan:
Run the example app on an Android < 15 device.

Rollback Plan:

Reviewed By: cortinico

Differential Revision: D77906644

Pulled By: alanleedev

fbshipit-source-id: 121cd6bc4133973f06b28eb9e79c9387ac7070a1
@cortinico
Copy link
Contributor

Hey @zoontek

Sadly this is still crashing, on release only and on Android 7 only with the following stacktrace:

2025-07-18 17:02:19.971  5958-5958  AndroidRuntime          com.facebook.react.uiapp             E  FATAL EXCEPTION: main
                                                                                                    Process: com.facebook.react.uiapp, PID: 5958
                                                                                                    java.lang.IllegalArgumentException: Context com.facebook.react.runtime.BridgelessReactContext@a6a81db is not a UiContext
                                                                                                    	at androidx.window.layout.util.ContextCompatHelper.unwrapUiContext$window_release(ContextCompatHelper.kt:52)
                                                                                                    	at androidx.window.layout.util.WindowMetricsCompatHelperBaseImpl.currentWindowMetrics(WindowMetricsCompatHelper.kt:84)
                                                                                                    	at androidx.window.layout.WindowMetricsCalculatorCompat.computeCurrentWindowMetrics(WindowMetricsCalculatorCompat.kt:39)
                                                                                                    	at com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetrics(DisplayMetricsHolder.kt:74)
                                                                                                    	at com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(DisplayMetricsHolder.kt:61)
                                                                                                    	at com.facebook.react.runtime.ReactInstance.<init>(ReactInstance.kt:243)
                                                                                                    	at com.facebook.react.runtime.ReactHostImpl.getOrCreateReactInstanceTask$lambda$41$lambda$37(ReactHostImpl.kt:919)
                                                                                                    	at com.facebook.react.runtime.ReactHostImpl.$r8$lambda$NwQSP6waC5OEu_tQNW6qI0fv5jo(ReactHostImpl.kt)
                                                                                                    	at com.facebook.react.runtime.ReactHostImpl$$ExternalSyntheticLambda11.then(D8$$SyntheticClass:0)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeImmediately$lambda$3(Task.kt:356)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$Companion.$r8$lambda$b7MTC0ufkn_2TjF4BGcNqGR9qK0(Task.kt)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$Companion$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Executors$ImmediateExecutor.execute(Executors.kt:41)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeImmediately(Task.kt:354)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$Companion.access$completeImmediately(Task.kt:250)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task.continueWith(Task.kt:132)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task.continueWith$default(Task.kt:117)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task.onSuccess$lambda$12(Task.kt:174)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task.$r8$lambda$l0wwo5AvbHxlwfI1R7CH_codxoo(Task.kt)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$$ExternalSyntheticLambda2.then(D8$$SyntheticClass:0)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$Companion.completeAfterTask$lambda$5(Task.kt:390)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$Companion.$r8$lambda$IPpysnehFwANEiW061YzapkMdoE(Task.kt)
                                                                                                    	at com.facebook.react.runtime.internal.bolts.Task$Companion$$ExternalSyntheticLambda4.run(D8$$SyntheticClass:0)
                                                                                                    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
                                                                                                    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
                                                                                                    	at java.lang.Thread.run(Thread.java:761)

@cortinico
Copy link
Contributor

cc @alanleedev

@alanleedev
Copy link
Contributor

alanleedev commented Jul 18, 2025

@zoontek @cortinico I think we may have to rethink this approach.

  1. Here is the code where exception is thrown;

It is checking for UiContext which is mainly Activity or InputMethodService.

  1. From the stack trace, we are passing in BridgelessReactContext from ReactInstace.kt which is not a UiContext.

I am not sure why this only happens on release build, why I wasn't getting a crash when testing on Android 7 emulator (build using BUCK) or how the issue was resolved for @zoontek with the fix. But seems like we may need to rethink about how this is handled.

@zoontek
Copy link
Contributor Author

zoontek commented Jul 21, 2025

@cortinico @alanleedev There's probably a race condition between initDisplayMetricsIfNotInitialized usages. If the first call is performed with an UI context, all good. If it's not, it fails.

Indeed, we need to rethink the approach. For me, initDisplayMetrics argument should be replaced by activity: Activity and usage must be fixed. Let me have a look, to see if this can be done quickly.

PS: Note that this isn't blocking for edgeToEdgeEnabled in 0.81, more of an additional fix.

@zoontek
Copy link
Contributor Author

zoontek commented Jul 21, 2025

@cortinico @alanleedev I've been able to reproduce the issue on Android 7, in release mode only (this doesn't occur in debug mode).

I switched initDisplayMetricsIfNotInitialized and initDisplayMetrics signatures to either accept a ReactContext? or a Activity? instead, and guarantee init only with the initial UI context passed.
I ran it on Android 7 debug and release mode, and various post Android 10 versions, debug and release mode, no crash.

The fix is here: https://github.com/zoontek/react-native/pull/1/files

EDIT: Here is the video. Note that I tested it with and without edgeToEdgeEnabled

demo.mp4

@cortinico
Copy link
Contributor

The fix is here: zoontek/react-native#1 (files)

Could you send this as a PR against facebook/react-native ?

@zoontek
Copy link
Contributor Author

zoontek commented Jul 21, 2025

@cortinico Sure thing, here it is: #52738

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Reverted Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dimensions.get('window').height not consistent in Android 14 and Android 15
10 participants