Skip to content

Nested Frame in TabViewItem: navigation bookkeeping stalls on both platforms (different mechanisms) #11444

Description

@aleclarson

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.

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions