| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
…entry A fragment can outlive the BackstackEntry it is bound to. Navigation only evicts fragments through transaction.replace(containerViewId, ...), so any fragment whose container no longer matches the frame's current container - left over from an activity recreation, a frame reset, or an interrupted navigation - stays added to the FragmentManager. _removeEntry then clears resolvedPage and drops entry.fragment without telling the FragmentManager, and the next transaction happily drives that fragment back through onCreateView. Every callback on that path either reported the condition with Trace.error - which routes to the error handler, rethrows, and becomes a fatal exception across the JNI boundary - or dereferenced the missing page directly, so the stale fragment took the app down instead of being discarded. - _removeEntry: detach the fragment's callbacks and remove it from the FragmentManager when it is still added, so it can neither be driven again nor resurrect the torn down page - findPageForFragment: discard an unclaimed restored fragment instead of throwing, and widen the entry lookup through _findEntryForTag so fragments restored from the backstack or the navigation queue are matched instead of treated as orphans - onDestroy: report a missing entry without throwing - onResume: bail out when the entry or page is gone rather than reading entry.resolvedPage.frame - onPause/onDestroyView: tolerate a missing frame
|
View your CI Pipeline Execution ↗ for commit f67ee37
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at 2026-08-17 15:10:07 UTC |
Sorry, something went wrong.
There was a problem hiding this comment.
Prevents Android’s FragmentManager from driving orphaned fragments after their navigation entries are discarded.
Changes:
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/core/ui/frame/index.android.ts | Detaches and removes fragments for discarded entries. |
| packages/core/ui/frame/frame-helper-for-android.ts | Adds orphan cleanup and lifecycle guards. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Sorry, something went wrong.
| } else { | ||
| // Android also restores fragments that were only in the backstack or still queued, so | ||
| // widen the lookup before treating this fragment as an orphan. | ||
| entry = frame._findEntryForTag(fragmentTag); |
| Trace.write(`Could not find a page for ${fragmentTag}. Discarding orphaned fragment.`, Trace.categories.NativeLifecycle, Trace.messageType.error); | ||
| removeFragmentIfAdded(fragment); |
| callbacks.entry = null; | ||
| callbacks.frame = null; |
| Back | FazBrowse Home | New Git URL |
PR Checklist
What is the current behavior?
A FragmentClass can outlive the BackstackEntry it is bound to, and the FragmentManager then drives it through the lifecycle against that dead entry. The reported symptom is a fatal:
Note <null> in the fragment's toString: the fragment's entry is intact, only resolvedPage is gone.
How the fragment gets stranded. Navigation only evicts fragments via transaction.replace(this.containerViewId, ...), which removes added fragments in that container. Frame._removeEntry then clears resolvedPage (FrameBase._removeEntry) and drops entry.fragment, but never tells the FragmentManager anything — it assumes the replace already did. That assumption breaks for any fragment whose container is no longer the frame's current container: leftovers from an activity recreation, a frame/root-view reset, or a navigation that was interrupted mid-flight. Such a fragment stays mAdded, so moveToExpectedState() keeps promoting it, and it gets onCreateView again after its entry has been discarded.
Why it is fatal rather than a no-op. Every callback on that path either reported the condition through Trace.error — which routes to DefaultErrorHandler, rethrows, and surfaces as an uncaught exception across the JNI boundary — or dereferenced the missing page directly:
This correlates strongly with memory pressure: the tighter memory gets, the more the OS destroys and recreates activities, which is what strands the fragments in the first place. It was reported from production with 300+ occurrences on low-RAM (4 GB) devices, mostly on resume after the app had been idle.
What is the new behavior?
The stale fragment is discarded instead of taking the app down, and it stops being stale in the first place:
Removals are always committed with commitAllowingStateLoss(): callers can be inside a FragmentManager transaction (findPageForFragment runs from onCreate, _removeEntry from a transition listener), where commitNow throws "FragmentManager is already executing transactions".
Also considered and left out: extending the restored-fragment cleanup in ActivityCallbacksImplementation.onCreate (which deliberately skips fragment* tags) to drop unclaimed NativeScript fragments. Having each orphan remove itself in its own onCreate is ordering-independent and also covers child FragmentManagers, which that cleanup does not walk.
No unit tests: packages/core specs cover parser/pure-logic modules only and nothing platform-specific, and these are FragmentManager lifecycle paths. Verification is on device — activity destroy/recreate (Don't keep activities) with a navigation in flight, plus tabs/bottom-navigation nested frames on resume.