Skip to content

[Android] Modal TabbedPage whose tabs are NavigationPage(ContentPage) is retained after PopModalAsync() #33918

@brunck

Description

@brunck

Description

On Android, a modal navigation flow that pushes a TabbedPage (via INavigation.PushModalAsync) leaks after it is popped when each tab is wrapped in its own NavigationPage. After PopModalAsync(), forcing a full GC (GC.Collect() + GC.WaitForPendingFinalizers() + GC.Collect()) does not reclaim the modal TabbedPage or its tab pages.

This can lead to unbounded memory growth in apps that repeatedly open/close modal tabbed workflows (a common pattern). This repro uses standard MAUI Navigation only (no Shell).

See the repro README for more information.

Steps to Reproduce

  • Deploy/run the reproduction app on Android.

  • On the main screen:
    Ensure Wrap tabs in NavigationPage is ON (default).
    Leave Iterations at the default (5), or set to any value > 1.

  • Tap Run repro N times.

  • Observe the Status output after each iteration. The app:
    Pushes a modal TabbedPage.
    Switches to the Settings tab.
    Pops the modal TabbedPage.
    Forces a full GC and updates Status with a “snapshot” line like:
    after pop i/N | Alive=x/y | Types=...

  • Optional: Tap Force GC (before / after) to capture and display snapshots without running the full loop (useful to demonstrate that a forced GC does not reduce the alive counts once the leak has occurred).

  • Optional: Tap Clear tracked refs to reset the in-app WeakReference tracking before re-running.

Expected Behavior

After await Navigation.PopModalAsync() completes and there are no remaining app references, the popped modal TabbedPage and its child pages should become eligible for collection and should be reclaimed by a forced full GC.

In the repro app, after each pop + forced GC, the tracked objects’ “Alive” count should return to 0 (or remain stable and not increase across iterations).

Actual Behavior

On Android, after each modal pop and forced full GC:

The popped modal TabbedPage remains alive.
The tab children remain alive (the per-tab NavigationPage wrappers).
The inner ContentPage inside each NavigationPage remains alive.
In the repro app, the Status output continues to show non-zero “Alive” counts after GC, and the alive counts increase as the loop repeats.

Link to public reproduction project repository

https://github.com/brunck/reproductions/tree/master/MauiTabbedModalLeakRepro

Version with bug

10.0.31

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Android 16 - API 36

Did you find any workaround?

Could not find a workaround.

Metadata

Metadata

Assignees

No one assigned

    Labels

    i/regressionThis issue described a confirmed regression on a currently supported versionpartner/syncfusionIssues / PR's with Syncfusion collaborationregressed-in-10.0.0s/triagedIssue has been revieweds/verifiedVerified / Reproducible Issue ready for Engineering Triaget/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions