Skip to content

[Android][Bridgeless] Destroying the Activity during a JS reload leaves ReactContext.getCurrentActivity() null until the app is backgrounded #59473

Open

Description

@mixin27

Description

With the New Architecture (bridgeless ReactHostImpl), if the host Activity is destroyed while a JS reload is still running, the reload finishes by resuming the new ReactContext with no Activity and marks the lifecycle RESUMED. When the app is reopened, ReactHost.onHostResume(activity) returns early because the state is "already resumed", so the new context never receives the new Activity.

From then on ReactContext.getCurrentActivity() is null for every native module, until the app goes to the background and comes back (onHostPause resets the state, and the next resume passes the Activity). React Native's own PermissionsAndroid.request() then rejects with E_INVALID_ACTIVITY ("Tried to use permissions API while not attached to an Activity."). We first hit it through a third-party recorder module whose constructor needs the current Activity.

This is likely the root cause of #46201 ("Tried to show an alert while not attached to an Activity", New Architecture). That issue was closed because it couldn't be reproduced; later comments report the same intermittent failure, fixed by restarting the app. Alert reads getCurrentActivity() the same way.

Where it goes wrong (packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/, unchanged on main at the time of writing):

  1. ReactHostImpl.kt, in the lifecycle step after instance creation (if (isReloading && !isManagerResumed), ~L1112 on main, L1124 in 0.86.3): isManagerResumed is read when the reload finishes. If the Activity was destroyed meanwhile, onHostDestroy(activity) → moveToHostDestroy() has already set the state to BEFORE_CREATE and currentActivity = null. The reload branch then calls reactLifecycleStateManager.moveToOnHostResume(reactContext, currentActivity) with currentActivity == null, which calls ReactContext.onHostResume(null) and sets the state to RESUMED. The comment above it assumes that during a reload "we must be on a React Native screen"; that no longer holds once the Activity is gone.
  2. ReactLifecycleStateManager.moveToOnHostResume() returns immediately when the state is already RESUMED.
  3. When the user reopens the app, ReactHostImpl.onHostResume(activity) updates ReactHostImpl.currentActivity, but because of (2) it never calls ReactContext.onHostResume(activity). ReactContext.mCurrentActivity, a weak reference, stays null.
  4. ReactContext.getCurrentActivity() returns null until the next pause/resume cycle or an onNewIntent.

In release builds this needs a JS reload, so it mainly affects development (Fast Refresh full reloads, R-R, the dev menu). Apps that reload JS programmatically, for example with OTA updates, could hit it in production.

Possible fixes (either would avoid the stale state):

  • Resume the context in the reload branch only when an Activity exists: if (isReloading && !isManagerResumed && currentActivity != null).
  • Or, in moveToOnHostResume(), when the state is already RESUMED but currentContext.currentActivity !== activity, still call currentContext.onHostResume(activity).

Steps to reproduce

  1. Install the reproducer with yarn android (debug build, Metro running from yarn start). The reproducer's metro.config.js delays bundle responses by 1.5 s. That emulates a real app's reload time; without it this tiny app reloads faster than Android tears down the Activity.
  2. Tap Call PermissionsAndroid.request(). It resolves (CAMERA isn't declared, so there's no dialog).
  3. Trigger a JS reload and press Back while it's running:
    adb shell "input keyevent 46 46; sleep 0.3; input keyevent 4" (R, R = reload, then Back). The Activity is destroyed before the reload finishes.
  4. Reopen the app from the launcher icon. Don't use a deep link: onNewIntent updates the context's Activity and hides the bug.
  5. Tap the button. It rejects with E_INVALID_ACTIVITY: Tried to use permissions API while not attached to an Activity.
  6. Press Home, reopen the app and tap again. It resolves.

Reproduced twice out of two runs on react-native 0.88.0 (Samsung Galaxy S21 Ultra, Android 15).

React Native Version

0.88.0

Affected Platforms

Runtime - Android

Areas

Bridgeless - The New Initialization Flow

Output of npx @react-native-community/cli info

System:
  OS: macOS 27.0.1
  CPU: (8) arm64 Apple M1 Pro
  Memory: 142.88 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 24.21.0
    path: /Users/mixin/.nvm/versions/node/v24.21.0/bin/node
  Yarn:
    version: 1.22.22
    path: /Users/mixin/.nvm/versions/node/v24.21.0/bin/yarn
  npm:
    version: 11.19.0
    path: /Users/mixin/.nvm/versions/node/v24.21.0/bin/npm
  Watchman:
    version: 2026.06.29.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /opt/homebrew/opt/ruby/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 27.0
      - iOS 27.0
      - macOS 27.0
      - tvOS 27.0
      - visionOS 27.0
      - watchOS 27.0
  Android SDK:
    API Levels:
      - "31"
      - "33"
      - "34"
      - "35"
      - "36"
      - "37"
    Build Tools:
      - 30.0.3
      - 33.0.1
      - 34.0.0
      - 35.0.0
      - 35.0.1
      - 36.0.0
      - 36.0.0
      - 37.0.0
    System Images:
      - android-35 | Google Play ARM 64 v8a
      - android-36 | Wear OS 6.0 ARM 64 v8a
      - android-36 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2026.1 AI-261.26222.65.2613.15948027
  Xcode:
    version: 27.0/27A266a
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.19
    path: /usr/bin/javac
  Ruby:
    version: 4.0.1
    path: /opt/homebrew/opt/ruby/bin/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.2.0
    wanted: 20.2.0
  react:
    installed: 19.3.0
    wanted: 19.3.0
  react-native:
    installed: 0.88.0
    wanted: 0.88.0
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: Not found
  newArchEnabled: false

Stacktrace or Logs

From the reproducer (react-native 0.88.0), adb logcat filtered to React Native's state-tracker lines:

10:30:34.311 W/unknown:BridgelessReact: ReactHost{0}.getOrCreateReloadTask(): Calling ReactContext.onHostPause()
10:30:34.749 W/unknown:BridgelessReact: ReactHost{0}.onHostPause(activity)
10:30:35.647 W/unknown:BridgelessReact: ReactHost{0}.onHostDestroy(activity)
10:30:36.264 W/unknown:BridgelessReact: ReactHost{0}.ReactContext.onHostResume()     <- reload finishes after the destroy and resumes the context with no Activity
10:30:55.141 W/unknown:BridgelessReact: ReactHost{0}.onHostResume(activity)          <- reopened from the launcher; no ReactContext.onHostResume() follows
JS: PermissionsAndroid.request() rejected: E_INVALID_ACTIVITY Tried to use permissions API while not attached to an Activity.
--- Home, then reopen ---
10:31:19.888 W/unknown:BridgelessReact: ReactHost{0}.onHostPause(activity)
10:31:19.895 W/unknown:BridgelessReact: ReactHost{0}.ReactContext.onHostPause()
10:31:22.375 W/unknown:BridgelessReact: ReactHost{0}.onHostResume(activity)
10:31:22.387 W/unknown:BridgelessReact: ReactHost{0}.ReactContext.onHostResume()     <- context gets the Activity again; the request resolves

Device: Samsung Galaxy S21 Ultra (SM-G998B), Android 15, debug build, New Architecture.

MANDATORY Reproducer

https://github.com/mixin27/rn-reload-current-activity

Screenshots and Videos

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions