Skip to content

[Android][Fabric] Screen content collapses to 1/density size on displays with a different density (Samsung DeX / freeform) — px→dp in updateState uses global density #4159

@ziponia

Description

@ziponia

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

  1. Build the reproducer below on a device with Samsung DeX or freeform windowing.
  2. Switch to DeX (or open the app in a freeform window on a display with a different density).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    platform:androidIssue related to Android part of the libraryrepro-providedA reproduction with a snack or repo is provided

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions