GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix(android): stop the FragmentManager driving fragments with a dead entry by edusperoni · Pull Request #11352 · NativeScript/NativeScript

Conversation

@edusperoni

@edusperoni edusperoni commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

PR Checklist

  • The PR title follows our guidelines: https://github.com/NativeScript/NativeScript/blob/master/tools/notes/CONTRIBUTING.md#commit-messages.
  • There is an issue for the bug/feature this PR is for.
  • You have signed the CLA.
  • All existing tests are passing.
  • Tests for the changes are included.

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:

com.tns.NativeScriptException: Calling js method onCreateView failed
Error: fragment0[0]<null>.onCreateView: entry has no resolvedPage
	at com.tns.FragmentClass.onCreateView(FragmentClass.java:55)
	at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3119)
	at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:577)
	at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:286)
	at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2211)
	at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2106)
	at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2049)

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:

  • onCreateView — fixed in fix(android): don't crash in Fragment.onCreateView on stale-fragment race #11264, but that only moved the crash one step later, since the fragment continues to…
  • onResume — this.entry.resolvedPage.frame throws a TypeError on the very same stale fragment
  • onDestroy — Trace.error on a missing entry, which is exactly what a discarded fragment hits on teardown
  • onPause / onDestroyView — this.frame.nativeViewProtected with no frame
  • findPageForFragment — throw new Error('Could not find a page for <tag>') for a restored fragment no live entry claims

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:

  • Frame._removeEntry — when the discarded entry still has a fragment, detach the fragment's callbacks (entry/frame) and remove it from its FragmentManager if it is still added. This is the actual leak fix: the guard only fires for fragments that are still added while their entry is being discarded, i.e. exactly the orphans. In normal forward/back/clearHistory/backstackVisible: false navigation the outgoing fragment has already been evicted by the replace before setCurrent runs, so isAdded() is false and nothing changes.
  • findPageForFragment — widen the entry lookup through the (previously unused) _findEntryForTag, which also searches the backstack and the navigation queue, so fragments Android restored from those are matched rather than declared orphans. When there genuinely is no entry, log and remove the fragment instead of throwing.
  • onDestroy / onResume / onPause / onDestroyView — treat a missing entry, page or frame as the recoverable state it is. onResume in particular had to be guarded, or the onCreateView fix from fix(android): don't crash in Fragment.onCreateView on stale-fragment race #11264 just relocates the crash.

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.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@edusperoni
fix(android): stop the FragmentManager driving fragments with a dead …
f67ee37
…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
@nx-cloud

nx-cloud Bot commented Aug 17, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit f67ee37

Command Status Duration Result
nx test apps-automated -c=ios ✅ Succeeded 2m 55s View ↗
nx run-many --target=test --configuration=ci --... ✅ Succeeded <1s View ↗

💡 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

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@edusperoni
edusperoni marked this pull request as ready for review August 17, 2026 16:33
@edusperoni
edusperoni requested a balanced review from Copilot August 17, 2026 16:52
Copilot started reviewing on behalf of edusperoni August 17, 2026 16:53
View session
Copilot AI reviewed Aug 17, 2026
View reviewed changes

Copilot AI left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents Android’s FragmentManager from driving orphaned fragments after their navigation entries are discarded.

Changes:

  • Removes still-added fragments when entries are discarded.
  • Expands restored-fragment lookup across backstack and navigation queues.
  • Adds null-safe lifecycle handling for stale fragments.

Reviewed 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread packages/core/ui/frame/frame-helper-for-android.ts
} 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);
Comment thread packages/core/ui/frame/frame-helper-for-android.ts
Comment on lines +62 to +63
Trace.write(`Could not find a page for ${fragmentTag}. Discarding orphaned fragment.`, Trace.categories.NativeLifecycle, Trace.messageType.error);
removeFragmentIfAdded(fragment);
Comment thread packages/core/ui/frame/frame-helper-for-android.ts
Comment on lines +92 to +93
callbacks.entry = null;
callbacks.frame = null;
@edusperoni
edusperoni marked this pull request as draft September 17, 2026 16:17
This was referenced Nov 5, 2026
[Android] Blank screen when a new activity adopts a shared root view before the old one stops #11615
Open
fix(android): keep the root view when an activity replaces another #11624
Open

This branch has not been deployed

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

Reviewers

Copilot code review Copilot
Copilot review effort
Applies to this pull request for everyone.Learn more about Copilot code review.
Copilot left review comments

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@edusperoni
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.