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 stall
iOS: 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 before loaded
leaves _executingContext stuck on iOS).
callLoaded() when isLoaded is stale (iOS).
- Register frames in an explicit stack registry rather than
Frame.topmost().
No working Android workaround found so far.
Summary
Hosting a
Frameas aTabViewItem.view(the per-tab navigation-stackpattern, 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 stopworking.
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
iOS:
isLoadedflips false → nav queue defers foreverObserved trace after tab selection:
From then on
frame.isLoaded === falsewhile the frame is on screen.Frame.navigate()enqueues (Navigation: NAVIGATEtraces) but_processNextNavigationEntry()early-returns on!isLoaded—NAVIGATE COREnever runs and the queue accumulates forever.Workaround:
frame.callLoaded()beforenavigate()re-arms theflag; pushes then complete (verified: push→pop cycles across a named
stack inside the tab all succeed).
Android: pushes commit but
setCurrentnever firesOn Android the same construction shows a different failure:
frame.currentPagenever advances past the initial page,backStackstays empty, and
goBack()no-ops (performGoBackfinds no backstackentry). The pushed fragment does mount natively.
Reading
ui/frame/fragment.transitions.android.js,setCurrentisinvoked from
transitionOrAnimationCompleted, driven byTransitionListener.onTransitionEnd. That listener never fires for thechild
FragmentManagertransactions of aTabViewItem-hosted frame —so
setCurrent/backStackbookkeeping never runs.animated:falsedoes not rescue; the completion path itself never fires.
Related
Frame.topmost()returns the innermost frame on Android once nestedframes exist, so app-level code can silently read the wrong frame —
worth guarding/documenting alongside this.
Ask
Confirm whether
TabViewItem.view = Frameis still the supportedparallel-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
loaded(navigating beforeloadedleaves
_executingContextstuck on iOS).callLoaded()whenisLoadedis stale (iOS).Frame.topmost().No working Android workaround found so far.