-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
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.