Skip to content

fix(android): embedded surface renders with zero height on RN 0.85#38

Open
akelmanson wants to merge 1 commit into
callstackincubator:mainfrom
akelmanson:fix/embedded-surface-zero-height-rn085
Open

fix(android): embedded surface renders with zero height on RN 0.85#38
akelmanson wants to merge 1 commit into
callstackincubator:mainfrom
akelmanson:fix/embedded-surface-zero-height-rn085

Conversation

@akelmanson

Copy link
Copy Markdown
Contributor

Summary

On RN 0.85 the embedded sandbox surface stays blank on Android: the bundle runs (Running "<name>" shows in the log) but the native SandboxReactNativeView is laid out with height 0 (width still fills via the parent's default alignItems: stretch, which masks the problem). The lib works on RN 0.80 — this is a 0.85 regression.

Two independent causes, both fixed:

  1. absoluteFillObject collapses to height 0. The native view is rendered as position:absolute (StyleSheet.absoluteFillObject) inside the user-styled <View> wrapper. On RN 0.85 an absolutely-positioned child with all-zero insets (top/left/right/bottom: 0) no longer stretches to a flex-sized parent — it collapses to height 0. Making it an in-flow flex: 1 child makes it reliably claim the wrapper's space.

  2. Nested ReactSurfaceView never measured. onLayout positioned the child with layout() but never called measure(). ReactSurfaceView only pushes layout constraints to its surface from onMeasure() (its onLayout() is a no-op until wasMeasured is true), so the nested surface kept its initial constructor constraints and rendered blank. We now measure() the child with EXACTLY specs before laying it out, so updateLayoutSpecs() runs with the real size.

Test plan

Verified on a device (RN 0.85.3 / React 19.2.3, New Architecture, Android/Waydroid x86_64): the embedded surface renders, lays out to fill its container, and receives touches. Before the change onLayout reported w=742 h=0; after, w=742 h=919 and the surface paints.

🤖 Generated with Claude Code

On RN 0.85 the embedded sandbox surface stays blank: the component runs
("Running <name>") but the native SandboxReactNativeView is laid out with
height 0. Two causes, both fixed here:

1. The native view is wrapped as position:absolute (StyleSheet.absoluteFillObject)
   inside the user-styled <View>. On 0.85 an absolutely-positioned child with
   all-zero insets no longer stretches to a flex-sized parent and collapses to
   height 0 (width still comes from the parent's default alignItems:stretch,
   masking it). Make the in-flow child flex:1 so it reliably claims the
   wrapper's space.

2. onLayout positioned the nested ReactSurfaceView via layout() but never
   measured it. ReactSurfaceView only pushes layout constraints to its surface
   from onMeasure() (its onLayout() is a no-op until wasMeasured). Measure the
   child with EXACTLY specs so updateLayoutSpecs() runs with the real size.

Verified on a device (RN 0.85.3): the embedded surface renders and lays out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines +308 to +312
// The native sandbox view fills the user-styled wrapper as an in-flow
// flex child. It used to be position:absolute (StyleSheet.absoluteFillObject),
// but on RN 0.85 an absolutely-positioned child with all-zero insets no longer
// stretches to a flex-sized parent — it collapses to height 0 and the embedded
// surface renders blank. flex:1 makes it claim the wrapper's space reliably.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

To be on the same page it work without issueson pre 0.85 too, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tried applying fix on android and ios RN80 demo apps:

Ios baseline (android baseline same, just diff backing color)
ios-demo-baseline

ios with fix applied
ios-demo-with-pr38-fix

android with fix applied
android-demo-with-pr38-fix


AI-assisted analysis

Finding: flex: 1 introduces layout regression on pre-0.85 apps (Android + iOS)

The change from StyleSheet.absoluteFillObject to flex: 1 alters how the native sandbox view interacts with its parent wrapper's padding.

Before (absoluteFillObject): The native view stretches to fill the entire wrapper bounds, ignoring any padding set on the wrapper <View style={style}>.

After (flex: 1): The native view participates in normal flex layout and respects the parent's padding, making the sandbox content area narrower/shorter.

Root cause

// In the library's index.tsx:
<View style={style}>          {/* ← user's style, may include padding */}
  <NativeSandboxReactNativeView style={_style} />  {/* ← was absoluteFill, now flex:1 */}
</View>

absoluteFillObject (position absolute + zero insets) ignores the parent's padding.
flex: 1 (in-flow child) respects it. Any consumer with padding on their sandbox component style will see a layout change.

Recommendation

The fix is correct for solving the RN 0.85 zero-height issue, but it's a breaking layout change for existing consumers. Consider one of:

  1. Strip padding from the inner wrapper — apply the user's style but override padding to 0 on the wrapper, or split into an outer container (user style) and inner container (no padding, holds the native view)
  2. Use width: '100%', height: '100%' instead of flex: 1 — this fills the content area explicitly without requiring flex participation, and behaves more like the old absolute approach while still being in-flow
  3. Document as a breaking change — if the old behavior (ignoring padding) was unintended, note it in release notes so consumers can remove padding from their sandbox styles

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants