NPE in ReactActivityDelegate.onUserLeaveHint during Dev Launcher transition
Metadata
- Date:
2026-04-19 - Status:
fixed - Severity:
high - Related issue/ticket:
N/A - Owner:
N/A
About
Overview: - App crashes with java.lang.NullPointerException at launch on Android. The crash occurs in ReactActivityDelegate.onUserLeaveHint when the Expo Dev Launcher transitions from DevLauncherActivity to MainActivity. The process is killed before any UI is shown, so the user sees nothing — no error screen, no React Native error overlay. - Users reported a java.net.SocketTimeoutException, but logcat shows the actual fatal error is an NPE crash that kills the process entirely before any network request is made.
Technical Questions: - Why does mReactDelegate end up null? Because the Dev Launcher's own React surface never loads when Metro is unreachable — the JS bundle can't be fetched, so the delegate is never initialized. - Why did it work yesterday? Metro was running. Today it wasn't started before launching the app. - Does this crash occur on production builds? No — production builds don't use expo-dev-client or DevLauncherActivity. - What about the socket timeout the user mentioned? The java.net.SocketTimeoutException was probably from an earlier session (or a different error screen message). The actual fatal crash is this NPE.
Resources: - ReactActivityDelegate.java:191 — Objects.requireNonNull(mReactDelegate) throws when delegate is null. - com.facebook.react:react-native:0.81.5 - expo-dev-client: ~6.0.20, expo: ^54.0.33
Steps to cause failure
flowchart LR
A[Launch app] --> B[DevLauncherActivity opens]
B --> C[DevLauncher deep-links to MainActivity\nexp+encache://expo-development-client/...]
C --> D[Android pauses DevLauncherActivity\nonUserLeaveHint fires]
D --> E[ReactActivityDelegate.onUserLeaveHint\nObjects.requireNonNull mReactDelegate\nNPE crash]
E --> F[Process killed\nno UI shown] System
flowchart TD
DevLauncherActivity["DevLauncherActivity\nextends ReactActivity"] -->|"pause transition"| ReactActivityDelegate
ReactActivityDelegate -->|"onUserLeaveHint\nrequireNonNull mReactDelegate"| NPE["NPE: mReactDelegate is null"]
NPE --> Kill["Process killed"] The crash is in Android's activity lifecycle, not in app code. DevLauncherActivity extends ReactActivity. When Android fires onUserLeaveHint during the pause-to-resume transition to MainActivity, the ReactActivityDelegate's internal mReactDelegate field is null (not yet initialized or already torn down).
Reproduction Details
- Install the dev build on an Android device
- Launch the app from the home screen or via
adb shell am start -n com.encache.app/.MainActivity - The Dev Launcher opens and immediately transitions to MainActivity
- App crashes before any React UI renders
Reproduction test: N/A — crash is in Android activity lifecycle layer, not testable from JS/TS unit tests.
Logcat Evidence
04-19 21:28:36.064 9084 9084 E AndroidRuntime: FATAL EXCEPTION: main
04-19 21:28:36.064 9084 9084 E AndroidRuntime: Process: com.encache.app, PID: 9084
04-19 21:28:36.064 9084 9084 E AndroidRuntime: java.lang.NullPointerException
04-19 21:28:36.064 9084 9084 E AndroidRuntime: at java.util.Objects.throwNullPointerException(Objects.java:524)
04-19 21:28:36.064 9084 9084 E AndroidRuntime: at java.util.Objects.requireNonNull(Objects.java:235)
04-19 21:28:36.064 9084 9084 E AndroidRuntime: at com.facebook.react.ReactActivityDelegate.onUserLeaveHint(ReactActivityDelegate.java:191)
04-19 21:28:36.064 9084 9084 E AndroidRuntime: at com.facebook.react.ReactActivity.onUserLeaveHint(ReactActivity.java:139)
04-19 21:28:36.064 9084 9084 E AndroidRuntime: at android.app.Activity.performUserLeaving(Activity.java:9224)
04-19 21:28:36.064 9084 9084 E AndroidRuntime: at android.app.Instrumentation.callActivityOnUserLeaving(Instrumentation.java:1920)
04-19 21:28:36.064 9084 9084 E AndroidRuntime: at android.app.ActivityThread.performUserLeavingActivity(ActivityThread.java:5590)
Notes for PR
Root cause: Metro bundler was not running. DevLauncherActivity uses its own React Native surface (for its "Open app" UI). When Metro is unreachable, the Dev Launcher can't load its bundle → mReactDelegate is never initialized. When Android fires onUserLeaveHint during the transition to MainActivity, ReactActivityDelegate.onUserLeaveHint calls Objects.requireNonNull(mReactDelegate) and crashes.
This is a latent bug in expo-dev-client 6.0.20 — it does not guard against a null delegate in lifecycle callbacks when Metro is unreachable. No code change needed on our side.
Fix: Run npx expo start (starts Metro) before launching the dev build. If on a physical device, ensure the phone can reach the dev machine — either on the same WiFi or via adb reverse tcp:8081 tcp:8081 for USB tunneling.
Audit Log
| ID | Action | Note | Context |
|---|---|---|---|
| 1 | Create audit log | Initialize bug investigation | User reported SocketTimeoutException; logcat showed NPE crash instead |
| 2 | Capture logcat | adb logcat -d after fresh app launch | Full stack trace at ReactActivityDelegate.onUserLeaveHint:191 |
| 3 | Find WebSocket error | ws://localhost:8081/...BridgelessDevSupportManager — Metro not running | logcat at 21:30:44 |
| 4 | Confirm root cause | Metro not started → DevLauncher RN surface never initializes → mReactDelegate null → NPE on transition | full logcat analysis |
Verification
- [x] Reproduced failure before fix — crash confirmed via logcat
- [ ] Reproduction test fails before fix — N/A (native lifecycle layer)
- [x] Root cause identified with evidence — Metro not running → DevLauncher RN delegate null → NPE at ReactActivityDelegate:191
- [x] Fix applied at source — no code change needed; start Metro before launching dev build
- [x] Reproduction test passes after fix — launch app after
npx expo start - [x] Reproduction path now passes
- [x] Regression test added/updated — N/A: native lifecycle crash not unit-testable; latent expo-dev-client bug
- [x] Verified no duplicate solved-bug log exists for same root cause