Skip to content

fix(Android): use per-display density for px→dp conversion in FabricEnabledViewGroup.updateState#4160

Open
ziponia wants to merge 1 commit into
software-mansion:mainfrom
ziponia:fix/android-multi-display-density-fabric-state
Open

fix(Android): use per-display density for px→dp conversion in FabricEnabledViewGroup.updateState#4160
ziponia wants to merge 1 commit into
software-mansion:mainfrom
ziponia:fix/android-multi-display-density-fabric-state

Conversation

@ziponia

@ziponia ziponia commented Jun 12, 2026

Copy link
Copy Markdown

Description

Fabric mounts views using the density of the display the surface is attached to, but FabricEnabledViewGroup.updateState converts the natively measured px size back to dp with the process-global density (PixelUtil), captured once from the device's main display.

On a display whose density differs from the main one (Samsung DeX, freeform multi-window, external monitors), every Screen pushes windowSize ÷ mainDisplayDensity into the Shadow Tree — all stack/tab content collapses into a 1/density-sized top-left box, while plain RN views outside screens track the window correctly.

Closes #4159.

Changes

  • FabricEnabledViewGroup.updateState (Android, Fabric): convert px→dp using the view's own context density (context.resources.displayMetrics.density), with a defensive fallback to PixelUtil.toDIPFromPixel if the density is not positive.
  • On single-display devices the context density equals the global density, so behavior there is unchanged.

Before & after - visual documentation

Before — content in a 1/density box After — content fills the window
before after

(The dark card is a diagnostic overlay that instruments updateState pushes. In the "after" screenshot the visible text rendering is correct because the app also applies a workaround for a separate react-native core bug — stale global density in text sp→px conversion — which is out of scope for this PR and reported to facebook/react-native.)

Measured updateState pushes on a Galaxy S25 Ultra (main display density 2.8125) in DeX, window resized 6 times:

window (dp) pushed state before fix pushed state after fix
394 × 702 140 × 235 394 × 662
497 × 730 177 × 245
737 × 794 262 × 268
421 × 788 150 × 266 421 × 748
338 × 727 338 × 687
429 × 790 429 × 750

Before the fix every push equals window ÷ 2.8125; after the fix it equals the window content size at every tested window size.

Test plan

  • Minimal reproducer (RN 0.86, New Architecture, based on the official reproducer template): https://github.com/ziponia/rn-multidisplay-density-repro — shows live on-screen measurements (outside-stack vs inside-ScreenStack size and the window ÷ content ratio).
  • Steps: yarn android on a DeX-capable device → switch to Samsung DeX (or open a freeform window on a different-density display) → inside-stack content size now matches the window, ratio ≈ 1; resize the window and verify it keeps tracking.
  • Verified on a physical Galaxy S25 Ultra (SM-S938N, One UI / Android 16) across 6 window sizes; phone (single display) behavior unchanged.

Checklist

  • Included code example that can be used to test this change (reproducer repo above).
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types — N/A, no public API changes.
  • Ensured that CI passes — pending CI on this PR.

Disclosure

This PR was authored by Claude (an AI coding agent) operating on behalf of and supervised by @ziponia, who hit this bug in a production app. The fix was validated on @ziponia's physical device, and this PR was reviewed and approved by them before submission.

…Group.updateState

Fabric mounts views using the density of the display the surface is attached to,
but updateState converted the measured px size back to dp with the process-global
density (PixelUtil), captured once from the device's main display. On displays with
a different density (Samsung DeX, freeform multi-window, external monitors) every
Screen pushed windowSize / mainDisplayDensity into the Shadow Tree, collapsing all
stack/tab content into a 1/density-sized box.

Convert with the view's own context density instead. On single-display devices both
values are identical, so behavior there is unchanged.

Fixes software-mansion#4159

@kkafar kkafar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hey! This patch looks good and the explanation here and in the #4159 sounds legit.

I'll check out the reproducer on Monday & if everything's good we'll land it.

Thanks!

@kkafar kkafar self-assigned this Jun 12, 2026
@kkafar kkafar self-requested a review June 12, 2026 14:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants