Skip to content

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

Closed

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

  1. self-assigned this
    on Sep 23, 2026
  2. CatchABus commented on Sep 23, 2026

    @CatchABus
    Contributor

    @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:

    frame.once('navigatedTo', () => frame.navigate({ create: () => page2 })); // Once navigation is done, perform a new navigation
    frame.navigate({ create: () => page1 });   // initial page — works
  3. NathanWalker commented on Sep 23, 2026

    @NathanWalker
    Contributor

    You can also try the suggested pull request mentioned directly to confirm:

    npm i https://pkg.pr.new/@nativescript/core@11446
    

    alongside @nativescript-community/octane 0.2.2

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