Skip to content

SafeAreaProvider renders nothing after an iOS background launch (initialWindowMetrics null and onInsetsChange never arrives) #774

Description

@christian-apollo

Summary

On iOS with the new architecture, when the OS launches the app in the background (CoreBluetooth state restoration, a CoreLocation region entry), initialWindowMetrics is null and SafeAreaProvider then depends entirely on one onInsetsChange event from RNCSafeAreaProviderComponentView. In that launch path the event is mostly lost, so SafeAreaProvider keeps insets === null and renders no children. When the user later opens the app they get a bare window (black in a dark theme, white in a light one) until they force quit. We had riders of a production app reporting exactly this as a "black screen when reopening the app".

Why initialWindowMetrics is null

RNCSafeAreaContext.getConstants reads RCTKeyWindow(). React Native's RCTKeyWindow only returns a window from a scene in foregroundActive or foregroundInactive state. A background launch has neither, so the constants carry initialWindowMetrics: null. That part is expected; the problem is what happens next.

Why the event is lost

RNCSafeAreaProviderComponentView.invalidateSafeAreaInsets sends once per view (_initialInsetsSent) and afterwards only when the insets or frame change. In a background launch the first layout passes happen while the app is not on screen, and by the time the app comes to the foreground the view's insets and frame no longer change, so no further event is sent. A recycled view whose frame does not change on re-use has the same problem for a remounted JS SafeAreaProvider, which starts from insets === null again.

Reproduction (iOS 26.4 simulator, deterministic enough)

  1. App with SafeAreaProvider initialMetrics={initialWindowMetrics} at the root and the location background mode, monitoring a CLCircularRegion.
  2. xcrun simctl location <sim> set a point 15 km outside the region, xcrun simctl terminate the app, then set the location inside the region. iOS relaunches the app in the background within about 20 s.
  3. xcrun simctl launch the app to bring it to the foreground.

Result on our app: blank window in 4 of 5 cycles. Inspecting the React tree over the Hermes inspector shows SafeAreaProvider with insets === null and no child fibers. Rotating the simulator forces a layout pass and the UI appears immediately, which confirms the provider was only waiting for the event.

Environment

  • react-native-safe-area-context 5.9.1
  • react-native 0.86.3, new architecture (Fabric), Hermes
  • iOS 26.4 simulator and iOS 26.6.1 devices

Proposed fix (what we ship as a patch-package)

Make sure every JS instance hears from the native view at least once, regardless of whether a layout pass would otherwise happen:

// RNCSafeAreaProviderComponentView.mm
- (void)updateEventEmitter:(const facebook::react::EventEmitter::Shared &)eventEmitter
{
  [super updateEventEmitter:eventEmitter];
  // A new JS instance (remount, or a recycled native view) must get its own send.
  _initialInsetsSent = NO;
}

- (void)didMoveToWindow
{
  [super didMoveToWindow];
  // Ask for a layout pass rather than sending here: safeAreaInsets are only
  // valid once the view has been laid out in the window, and a frame that did
  // not change would otherwise get no layout pass at all.
  if (self.window != nil) {
    [self setNeedsLayout];
  }
}

With this patch the same 5 relaunch cycles rendered 5 of 5, and a normal launch still produces the same small number of onInsetsChange sends (no extra traffic on background and foreground transitions).

On the JS side we also stopped passing null as initialMetrics: we persist the last insets the provider settled on and pass those (on the window frame from Dimensions) when initialWindowMetrics is null, so the tree renders immediately and the native report corrects it. It may be worth documenting that initialWindowMetrics is null on background launches, since the README presents it as a way to render on the first frame.

Happy to open a PR with the native change if you would take it.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions