Skip to content

fix(core): rebuild nested-frame fragment when its view was destroyed on reattach#11214

Open
dangrima90 wants to merge 1 commit into
NativeScript:mainfrom
dangrima90:fix/nested-frames-android
Open

fix(core): rebuild nested-frame fragment when its view was destroyed on reattach#11214
dangrima90 wants to merge 1 commit into
NativeScript:mainfrom
dangrima90:fix/nested-frames-android

Conversation

@dangrima90
Copy link
Copy Markdown
Contributor

PR Checklist

What is the current behavior?

As described on discord I have encountered an issue where a nested frame results in having no content. As stated I have investigated this via claude, not mentioning it to not take any responsibility 😄 but to highlight that code changes and comments in the code were added by claude. I did direct it to simplify certain comments.

As I mentioned on discord I did try replicating this issue in a clean application but I didn't manage. My suspicion was that there's was minor issue in NativeScript that's being triggered by as specific use case in my application.

What is the new behavior?

Short description via claude of this update:

The update here extends the _ensureEntryFragment recovery from #10713 to cover the case where the fragment object survives but its view has been destroyed. Without this, the nested frame renders blank after the parent is briefly covered and dismissed. Also guards entry.fragment = null in onDestroy so the old fragment's teardown doesn't overwrite the new reference assigned by the rebuild path.

Two changes under packages/core/ui/frame:

  1. index.android.ts_ensureEntryFragment also rebuilds when fragment.getView() == null, gated by a new _pendingFragmentCheck flag set by onLoaded(). The flag is needed because _onAttachedToWindow also calls _ensureEntryFragment during outgoing transitions, where a null view is expected and a rebuild would be wrong.

  2. frame-helper-for-android.tsonDestroy only nulls out entry.fragment if it still points to the destroying fragment. The rebuild above assigns a new fragment to entry.fragment, and without this guard the old fragment's destruction would overwrite that new reference.

…on reattach

Extends the _ensureEntryFragment recovery from NativeScript#10713 to cover the case where the fragment object survives but its view has been destroyed. Without this, the nested frame renders blank after the parent is briefly covered and dismissed. Also guards entry.fragment = null in onDestroy so the old fragment's teardown doesn't overwrite the new reference assigned by the rebuild path.
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 12, 2026

View your CI Pipeline Execution ↗ for commit 5df98bd

Command Status Duration Result
nx test apps-automated -c=android ✅ Succeeded 4m 39s View ↗
nx run-many --target=test --configuration=ci --... ✅ Succeeded 3s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-12 17:09:34 UTC

@CatchABus
Copy link
Copy Markdown
Contributor

CatchABus commented May 12, 2026

I paste the video indicating the problem here:
https://github.com/user-attachments/assets/d627e4e2-2592-4cef-b657-58523d13a56f

@NathanWalker NathanWalker requested a review from Copilot May 13, 2026 19:17
@NathanWalker
Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes a bug where a nested frame renders blank after its parent is briefly covered and dismissed by rebuilding the entry fragment when its view has been destroyed during reattach.

Changes:

  • Extend _ensureEntryFragment to rebuild when fragment.getView() == null, gated by a new _pendingFragmentCheck flag set in onLoaded() to distinguish incoming reattach from outgoing transitions.
  • Guard entry.fragment = null in onDestroy so it only nulls when the entry still references the destroying fragment, preserving any newer fragment assigned by the rebuild path.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
packages/core/ui/frame/index.android.ts Adds _pendingFragmentCheck flag and broadens fragment-rebuild condition to cover destroyed-view case.
packages/core/ui/frame/frame-helper-for-android.ts Only clears entry.fragment in onDestroy if it still references the destroying fragment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Review Change Stack

Walkthrough

Android Frame navigation recovery during nested transitions now includes two guarded checks: fragment cleanup only nullifies stale references, and fragment rebuild only occurs during onLoaded() passes when the view is missing, not during outgoing transition teardown.

Changes

Android Fragment Navigation Recovery

Layer / File(s) Summary
Fragment cleanup guard in onDestroy
packages/core/ui/frame/frame-helper-for-android.ts
FragmentCallbacksImplementation.onDestroy conditionally nulls entry.fragment only when it still references the fragment being destroyed, preventing overwrites of newer fragment references during nested recovery.
Fragment rebuild condition with pending check
packages/core/ui/frame/index.android.ts
Frame adds _pendingFragmentCheck flag to track onLoaded() calls to _ensureEntryFragment(). The rebuild condition is refactored to only recreate the fragment when the view is null during onLoaded() passes, blocking unwanted recreation during outgoing transition cleanup.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • NativeScript/NativeScript#10713: Both PRs modify the fragment recreation path in _ensureEntryFragment during onLoaded() entry handling, with this PR's _pendingFragmentCheck guard complementing nested-frame fragment management.

Suggested reviewers

  • NathanWalker

Poem

🐰 In Android's frame, a fragment was born,
But chaos came with each nested morn,
Guards now stand at cleanup's door,
Rebuild waits for onLoaded's call,
Navigation smooth, no more overwrites tall!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: rebuilding nested-frame fragment when its view was destroyed on reattach, which aligns with the changeset's core purpose.
Description check ✅ Passed The description is directly related to the changeset, explaining the bug scenario, the new behavior, and the specific code changes made across both modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/ui/frame/index.android.ts`:
- Around line 87-92: The delayed callback in _ensureEntryFragment reads the
instance field _pendingFragmentCheck which may change before the timeout fires;
capture the current value into a local constant (e.g., const pendingAtSchedule =
this._pendingFragmentCheck) immediately before scheduling the timeout and use
that local within the timeout to decide whether to rebuild the fragment; update
every similar scheduling site (the calls around lines with
_ensureEntryFragment/_onAttachedToWindow handling and the other occurrences you
noted) to use the captured local instead of reading this._pendingFragmentCheck
inside the async callback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3082298b-167d-4c3c-86fe-4f4b1bd88f5e

📥 Commits

Reviewing files that changed from the base of the PR and between 0c8229c and 5df98bd.

📒 Files selected for processing (2)
  • packages/core/ui/frame/frame-helper-for-android.ts
  • packages/core/ui/frame/index.android.ts

Comment on lines +87 to +92
// True while inside an onLoaded()-driven _ensureEntryFragment call. _ensureEntryFragment
// also runs from _onAttachedToWindow during outgoing navigations, where the fragment's
// view is null because it's being torn down on purpose — we must not rebuild it then.
// This flag lets _ensureEntryFragment tell the two cases apart.
private _pendingFragmentCheck = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Capture the rebuild-origin flag before scheduling the timeout.

_pendingFragmentCheck is read inside the delayed callback, but it can be changed by later lifecycle calls before that callback runs. That can incorrectly enable/disable fragment rebuilds across overlapping _ensureEntryFragment() invocations.

Proposed fix
 onUnloaded() {
 	super.onUnloaded();
+	this._pendingFragmentCheck = false;

 	if (typeof this._frameCreateTimeout === 'number') {
 		clearTimeout(this._frameCreateTimeout);
 		this._frameCreateTimeout = null;
 	}
 }

 private _ensureEntryFragment(): void {
+	const shouldRebuildMissingView = this._pendingFragmentCheck;
+	this._pendingFragmentCheck = false;
+
 	// in case the activity is "reset" using resetRootView or disposed we must wait for
 	// the attachedToWindow event to make the first navigation or it will crash
 	// https://github.com/NativeScript/NativeScript/commit/9dd3e1a8076e5022e411f2f2eeba34aabc68d112
 	// though we should not do it on app "start"
 	// or it will create a "flash" to activity background color
 	if (this._isReset && !this._attachedToWindow) {
-		this._pendingFragmentCheck = false;
 		return;
 	}

 	this._frameCreateTimeout = setTimeout(() => {
 		// there's a bug with nested frames where sometimes the nested fragment is not recreated at all
 		// so we manually check on loaded event if the fragment is not recreated and recreate it
 		const currentEntry = this._currentEntry || this._executingContext?.entry;
 		if (currentEntry) {
 			const fragmentNeedsRebuild =
-				!currentEntry.fragment || (currentEntry.fragment.getView() == null && this._pendingFragmentCheck);
+				!currentEntry.fragment || (currentEntry.fragment.getView() == null && shouldRebuildMissingView);
 			if (fragmentNeedsRebuild) {
 				const manager = this._getFragmentManager();
 				const transaction = manager.beginTransaction();
 				currentEntry.fragment = this.createFragment(currentEntry, currentEntry.fragmentTag);
 				_updateTransitions(currentEntry);
 				transaction.replace(this.containerViewId, currentEntry.fragment, currentEntry.fragmentTag);
 				transaction.commitAllowingStateLoss();
 			}
 		}

-		this._pendingFragmentCheck = false;
 		this._frameCreateTimeout = null;
 	}, 0);
 }

Also applies to: 254-257, 287-288, 291-313

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/ui/frame/index.android.ts` around lines 87 - 92, The delayed
callback in _ensureEntryFragment reads the instance field _pendingFragmentCheck
which may change before the timeout fires; capture the current value into a
local constant (e.g., const pendingAtSchedule = this._pendingFragmentCheck)
immediately before scheduling the timeout and use that local within the timeout
to decide whether to rebuild the fragment; update every similar scheduling site
(the calls around lines with _ensureEntryFragment/_onAttachedToWindow handling
and the other occurrences you noted) to use the captured local instead of
reading this._pendingFragmentCheck inside the async callback.

Copy link
Copy Markdown
Collaborator

@farfromrefug farfromrefug left a comment

Choose a reason for hiding this comment

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

It seems fine to me. I am not sure about code rabbit proposed changr though. I am having a hard time wrapping my head around the flow

@CatchABus
Copy link
Copy Markdown
Contributor

It seems fine to me. I am not sure about code rabbit proposed changr though. I am having a hard time wrapping my head around the flow

The author and I are doing a few last tests to see if an old PR has caused the regression.
If we don't find anything else, I'll also leave my approval here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants