Description
On Android displays whose density differs from the device's main display (Samsung DeX, freeform multi-window, external monitors), all ScreenStack / Tabs content is squeezed into a small top-left box of exactly windowSize ÷ mainDisplayDensity, while plain RN views outside any Screen track the window correctly.
Root cause: FabricEnabledViewGroup.updateState converts the natively measured px size back to dp with PixelUtil.toDIPFromPixel — a process-global density captured once from the main display — but Fabric mounted the view using the per-display density. The mismatched round-trip pushes a shrunken frameWidth / frameHeight into the Shadow Tree, so the whole JS subtree lays out at 1/density scale. The same lines are present on latest main.
Real-device evidence (Galaxy S25 Ultra, main display density 2.8125, instrumented updateState, DeX window resized 6 times):
| window (dp) |
Screen state push (dp) |
ratio |
| 394 × 702 |
140 × 235 |
2.81 |
| 497 × 730 |
177 × 245 |
2.81 |
| 737 × 794 |
262 × 268 |
2.81 |
| 304 × 452 |
108 × 146 |
2.81 |
| 421 × 788 |
150 × 266 |
2.81 |
Every push is exactly window ÷ 2.8125. Screenshots (before/after) are in the reproducer README.
Verified fix (on device): convert using the view's own context density — context.resources.displayMetrics.density — which is identical to the global value on single-display devices (no behavior change) and correct on multi-display setups. After this change the pushed state equals the window size at every tested window size, and content fills the window. A PR follows shortly.
(Related Fabric size-sync reports, likely same family: #2803, #2933. Note: a second, independent core-RN bug — stale global density in text sp→px — also surfaces in this scenario; it is out of scope here and will be reported to facebook/react-native.)
Steps to reproduce
- Build the reproducer below on a device with Samsung DeX or freeform windowing.
- Switch to DeX (or open the app in a freeform window on a display with a different density).
- The app displays live measurements:
root onLayout (OUTSIDE stack) ≈ window size, while INSIDE ScreenStack content ≈ window ÷ phoneDensity, with the ratio shown on screen.
Snack or a link to a repository
https://github.com/ziponia/rn-multidisplay-density-repro
Screens version
4.25.2 (bug confirmed present on main as well)
React Native version
0.86.0 (reproducer) / 0.85.3 (production app where it was first hit)
Platforms
Android
JavaScript runtime
Hermes
Workflow
Bare (reproducer) — also reproduced in an Expo SDK 56 production app
Architecture
Fabric (New Architecture)
Build type
Reproduced in both Debug and Release
Device
Real device — Samsung Galaxy S25 Ultra (SM-S938N), One UI / Android 16, Samsung DeX
Disclosure
This issue was authored by Claude (an AI coding agent) operating on behalf of and supervised by @ziponia, who hit this bug in a production app. All measurements were taken on @ziponia's physical device, and this report was reviewed and approved by them before submission.
Description
On Android displays whose density differs from the device's main display (Samsung DeX, freeform multi-window, external monitors), all
ScreenStack/Tabscontent is squeezed into a small top-left box of exactlywindowSize ÷ mainDisplayDensity, while plain RN views outside any Screen track the window correctly.Root cause:
FabricEnabledViewGroup.updateStateconverts the natively measured px size back to dp withPixelUtil.toDIPFromPixel— a process-global density captured once from the main display — but Fabric mounted the view using the per-display density. The mismatched round-trip pushes a shrunkenframeWidth/frameHeightinto the Shadow Tree, so the whole JS subtree lays out at1/densityscale. The same lines are present on latestmain.Real-device evidence (Galaxy S25 Ultra, main display density 2.8125, instrumented
updateState, DeX window resized 6 times):Every push is exactly
window ÷ 2.8125. Screenshots (before/after) are in the reproducer README.Verified fix (on device): convert using the view's own context density —
context.resources.displayMetrics.density— which is identical to the global value on single-display devices (no behavior change) and correct on multi-display setups. After this change the pushed state equals the window size at every tested window size, and content fills the window. A PR follows shortly.(Related Fabric size-sync reports, likely same family: #2803, #2933. Note: a second, independent core-RN bug — stale global density in text sp→px — also surfaces in this scenario; it is out of scope here and will be reported to facebook/react-native.)
Steps to reproduce
root onLayout (OUTSIDE stack)≈ window size, whileINSIDE ScreenStackcontent ≈window ÷ phoneDensity, with the ratio shown on screen.Snack or a link to a repository
https://github.com/ziponia/rn-multidisplay-density-repro
Screens version
4.25.2 (bug confirmed present on
mainas well)React Native version
0.86.0 (reproducer) / 0.85.3 (production app where it was first hit)
Platforms
Android
JavaScript runtime
Hermes
Workflow
Bare (reproducer) — also reproduced in an Expo SDK 56 production app
Architecture
Fabric (New Architecture)
Build type
Reproduced in both Debug and Release
Device
Real device — Samsung Galaxy S25 Ultra (SM-S938N), One UI / Android 16, Samsung DeX
Disclosure
This issue was authored by Claude (an AI coding agent) operating on behalf of and supervised by @ziponia, who hit this bug in a production app. All measurements were taken on @ziponia's physical device, and this report was reviewed and approved by them before submission.