From 26d34e136d844919f1bcd04ccdea2f5ea3d765ac Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Tue, 9 Jun 2026 15:07:03 -0700 Subject: [PATCH] fix(android): don't crash in Fragment.onCreateView on stale-fragment race onCreateView already returns null when its entry, resolvedPage, or frame is missing, but it first called Trace.error, which rethrows through the registered error handler across the JNI boundary and turns the recoverable case into a fatal "Calling js method onCreateView failed" crash. These nulls happen when the FragmentManager drives a stale fragment to CREATE_VIEW after NativeScript has already torn down the corresponding backstack entry (process death / activity recreation after rotation or theme change, clearHistory, or rapid navigate+goBack). The fragment should just be discarded, not crash the app. Downgrade the three guards to Trace.messageType.warn so the existing return-null recovery path is actually reached, consistent with the existing onDestroy handling of the same race. --- .../core/ui/frame/frame-helper-for-android.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/core/ui/frame/frame-helper-for-android.ts b/packages/core/ui/frame/frame-helper-for-android.ts index d50236d4e7..63281c0ae0 100644 --- a/packages/core/ui/frame/frame-helper-for-android.ts +++ b/packages/core/ui/frame/frame-helper-for-android.ts @@ -112,21 +112,33 @@ export class FragmentCallbacksImplementation implements AndroidFragmentCallbacks const entry = this.entry; if (!entry) { - Trace.error(`${fragment}.onCreateView: entry is null or undefined`); + // Recoverable race: a stale fragment is being driven to onCreateView by the + // FragmentManager after its entry was cleared. + // Using Trace.error (routes to an error handler, which throws) would be fatal as the error handler rethrows across the JNI boundary. + if (Trace.isEnabled()) { + Trace.write(`${fragment}.onCreateView: entry is null or undefined`, Trace.categories.NativeLifecycle, Trace.messageType.warn); + } return null; } const page = entry.resolvedPage; if (!page) { - Trace.error(`${fragment}.onCreateView: entry has no resolvedPage`); + // Logging via Trace.error (routes to an error handler, which throws) here is fatal because the registered error handler rethrows across the JNI boundary. + if (Trace.isEnabled()) { + Trace.write(`${fragment}.onCreateView: entry has no resolvedPage`, Trace.categories.NativeLifecycle, Trace.messageType.warn); + } return null; } const frame = this.frame; if (!frame) { - Trace.error(`${fragment}.onCreateView: this.frame is null or undefined`); + // Recoverable race: the owning frame was already torn down. Returning null discards + // this fragment; using Trace.error (routes to an error handler, which throws) would be fatal as the error handler rethrows across the JNI boundary. + if (Trace.isEnabled()) { + Trace.write(`${fragment}.onCreateView: this.frame is null or undefined`, Trace.categories.NativeLifecycle, Trace.messageType.warn); + } return null; }