Repository navigation
Nested Frame in TabViewItem: navigation bookkeeping stalls on both platforms (different mechanisms) #11444
Description
Summary
Hosting a Frame as a TabViewItem.view (the per-tab navigation-stack
pattern, which core even special-cases via _pushInFrameStackRecursive)
leaves the frame's navigation bookkeeping broken on both platforms —
but through different mechanisms. Pushes may execute natively while
frame.currentPage / frame.backStack / frame.goBack() silently stop
working.
Found in a real app driving navigation programmatically after tab
selection; reproduced deterministically on @nativescript/core 9.1.2,
@nativescript/ios 9.1.0, @nativescript/android 9.1.1.
Construction
const tabView = new TabView();
const item = new TabViewItem();
item.title = 'Demos';
const frame = new Frame();
item.view = frame; // per-tab stack
tabView.items = [item, ...];
// After the tab is selected and the frame is loaded:
frame.navigate({ create: () => page1 }); // initial page — works
frame.navigate({ create: () => page2 }); // subsequent pushes stalliOS: isLoaded flips false → nav queue defers forever
Observed trace after tab selection:
frame 'loaded' fires → frame.navigate() → NAVIGATE CORE (page mounts)
frame 'unloaded' fires shortly after (lifecycle churn from item views,
which skip the normal parent/load path)
frame 'loaded' fires again
From then on frame.isLoaded === false while the frame is on screen.
Frame.navigate() enqueues (Navigation: NAVIGATE traces) but
_processNextNavigationEntry() early-returns on !isLoaded —
NAVIGATE CORE never runs and the queue accumulates forever.
Workaround: frame.callLoaded() before navigate() re-arms the
flag; pushes then complete (verified: push→pop cycles across a named
stack inside the tab all succeed).
Android: pushes commit but setCurrent never fires
On Android the same construction shows a different failure:
Navigation: NAVIGATE
Navigation: NAVIGATE CORE(Page<demo-page>); currentPage: Page(129)
— fragment transaction commits; the pushed page's fragment is created —
Navigation: GO BACK
— no GO BACK CORE; backStack is empty —
frame.currentPage never advances past the initial page, backStack
stays empty, and goBack() no-ops (performGoBack finds no backstack
entry). The pushed fragment does mount natively.
Reading ui/frame/fragment.transitions.android.js, setCurrent is
invoked from transitionOrAnimationCompleted, driven by
TransitionListener.onTransitionEnd. That listener never fires for the
child FragmentManager transactions of a TabViewItem-hosted frame —
so setCurrent/backStack bookkeeping never runs. animated:false
does not rescue; the completion path itself never fires.
Related
Frame.topmost() returns the innermost frame on Android once nested
frames exist, so app-level code can silently read the wrong frame —
worth guarding/documenting alongside this.
Ask
Confirm whether TabViewItem.view = Frame is still the supported
parallel-stacks construction on both platforms. If yes, the
loaded/bookkeeping propagation for item-hosted frames needs a fix; if
not, guidance on the blessed pattern (per-tab stacks that keep the tab
bar visible) would be appreciated.
Workarounds used in-app
- Mount the pane's first page on
loaded(navigating beforeloaded
leaves_executingContextstuck on iOS). callLoaded()whenisLoadedis stale (iOS).- Register frames in an explicit stack registry rather than
Frame.topmost().
No working Android workaround found so far.
Activity
CatchABus commentedon Sep 23, 2026
@aleclarson Being in the view "loaded lifecycle" doesn't necessarily mean the navigation is over.
It's ideal to wait for one navigation to be done before proceeding to another because there's always a chance for current navigation to interrupt ungracefully.
I'd retry your scenario with the following:
NathanWalker commentedon Sep 23, 2026
You can also try the suggested pull request mentioned directly to confirm:
alongside
@nativescript-community/octane0.2.2