DEV Community

Cover image for LeakAnalyser 1.0.1: See Which MAUI View Survives After You Pop It
NuvyntraLabs
NuvyntraLabs

Posted on

LeakAnalyser 1.0.1: See Which MAUI View Survives After You Pop It

A page that leaves the navigation stack should become collectable. If a handler, a binding, or a static root still points at it, the next push pays for the last one.

Plugin.Maui.LeakAnalyser answers that one question. Version 1.0.1 is on nuget.org. After a view looks finished, it holds a WeakReference, forces GC, and reports whether the object is still alive. Detection stays in Debug. Teardown can stay in Release.

This post is the walkthrough: detect-only in Debug, disconnect handlers when you are ready, and which pages you must suppress because they are cached on purpose.

What LeakAnalyser is

LeakAnalyser is a liveness test for .NET MAUI pages and views on Android, iOS, Mac Catalyst, and Windows. MIT licensed. It targets net10.0, net10.0-android (API 21+), net10.0-ios and net10.0-maccatalyst (15+), and net10.0-windows (10.0.17763+; the Windows TFM is included when the package is packed on Windows).

It does one job: report that a finished view is still rooted. A profiler tells you why.

Package Plugin.Maui.LeakAnalyser 1.0.1
Registration UseLeakAnalyser
Monitor LeakMonitor.Cascade
Teardown TearDown.Cascade with DisconnectHandlers or Compartmentalize
Source github.com/nuvyntralabs/Plugin.Maui.LeakAnalyser
Docs nuvyntralabs.github.io/packages/plugin-maui-leak-analyser/
Guides Get started · How it works

The API is UseLeakAnalyser, LeakMonitor, TearDown, and LeakGraph. It is inspired by AdamE.MemoryToolkit.Maui. It is not a drop-in fork of that toolkit. 1.0.1 does not change the API.

Debug: detect only

Forced GC is a probe. Keep UseLeakAnalyser and LeakMonitor.Cascade inside DEBUG.

using Plugin.Maui.LeakAnalyser;

#if DEBUG
builder.Logging.AddDebug();
builder.UseLeakAnalyser(options =>
{
    options.OnLeaked = target => { /* alert, using target.Name */ };
    options.DefaultTearDownStrategy = TearDownStrategy.DetectOnly;
});
#endif
Enter fullscreen mode Exit fullscreen mode
<ContentPage xmlns:la="clr-namespace:Plugin.Maui.LeakAnalyser;assembly=Plugin.Maui.LeakAnalyser"
             la:LeakMonitor.Cascade="True"
             la:LeakMonitor.Name="OrdersPage">
Enter fullscreen mode Exit fullscreen mode

OnLeaked fires when the target is still alive after the collection passes. OnCollected fires when it is collected. Collection is the healthy outcome. CollectionTarget carries Name, ObjectType, and Screen. Defaults are 10 collections, 200 milliseconds apart.

The monitor waits until the view looks finished: a page popped from an active NavigationPage, a detached template, or a short wait that skips anything still on the navigation stack, still hosted by Shell, or still under a tab. Cached pages and tabs stay alive on purpose. Suppress those, or they look like leaks.

Disconnect handlers

DisconnectHandlers is the default when you omit Strategy. It walks children and calls DisconnectHandler(). It leaves BindingContext, Content, and Parent in place. It honors HandlerDisconnectPolicy.Manual and TearDown.Suppress.

Put TearDown after LeakMonitor so teardown does not run before the snapshot.

la:LeakMonitor.Cascade="True"
la:TearDown.Cascade="True"
la:TearDown.Strategy="DisconnectHandlers"
Enter fullscreen mode Exit fullscreen mode

The same attached properties exist in C#: LeakMonitor.SetCascade, TearDown.SetCascade, page.Monitor(), and page.TearDown(TearDownStrategy.DisconnectHandlers).

In Release, teardown can stay on without detection:

la:TearDown.Cascade="True"
Enter fullscreen mode Exit fullscreen mode

Do not call UseLeakAnalyser or set LeakMonitor.Cascade in Release.

Compartmentalize

Compartmentalize clears common managed references, runs TearDown.OnTearDown while the element still has a handler, then disconnects. Each clear is isolated. A throwing setter is logged and teardown continues.

It clears BindingContext, Parent, logical children, behaviors, resources, gesture recognizers, formatted text, item sources, and Content on content views, borders, pages, and scroll views. CommunityToolkit hooks stay, because they are attached properties.

Opt into it per page. A Shell flyout or a NavigationPage that still holds modals will blank if this runs too early.

la:TearDown.Strategy="Compartmentalize"
Enter fullscreen mode Exit fullscreen mode

A breadcrumb, not a crash

LeakAnalyser does not reference Diagnostics or Observability. Forward OnLeaked yourself when the host already has those plugins. Register Diagnostics first if you want its logger ready before the first callback.

#if DEBUG
builder.UseMauiDiagnostics();
builder.UseLeakAnalyser(options =>
{
    options.OnLeaked = target =>
    {
        MauiDiagnostics.TrackEvent($"Leak:{target.Name}");
        MauiDiagnostics.TrackException(
            new InvalidOperationException($"{target.Name} survived forced GC."));
    };
});
#endif
Enter fullscreen mode Exit fullscreen mode

Track OnLeaked only. A collected target is success. Forced GC counts are not an app-health signal.

When something looks wrong

What you see What to do
Every Shell tab reports a leak Cached pages stay alive on purpose. LeakMonitor.SetSuppress and TearDown.SetSuppress on that host.
A leak appears while the browser sheet is open Temporary unloads such as Browser.OpenAsync should suppress the host NavigationPage, then clear Suppress on Loaded.
A template root leaks and the page does not Put Cascade on each ControlTemplate root, not only on the host control.
OnLeaked names the page and you still do not know the root Open Instruments, dotMemory, or Visual Studio diagnostics. This package reports liveness.
Release builds feel slower after navigation LeakMonitor.Cascade or UseLeakAnalyser is still compiled into Release. Detection forces GC.

Where LeakAnalyser sits next to the other tools

Need Tool
Is this finished view still alive? LeakAnalyserPlugin.Maui.LeakAnalyser
The retain path Instruments, dotMemory, or Visual Studio diagnostics
Crash, ANR, and breadcrumbs Plugin.Maui.Diagnostics
Startup, page, and API timings Plugin.Maui.Performance

AdamE.MemoryToolkit.Maui is the usual community toolkit for the same class of problem. LeakAnalyser is the package when you want LeakMonitor plus an optional DisconnectHandlers or Compartmentalize pass, and you already accept that the retain path lives in a profiler.

Try it

dotnet add package Plugin.Maui.LeakAnalyser --version 1.0.1
Enter fullscreen mode Exit fullscreen mode

In Debug, call UseLeakAnalyser with TearDownStrategy.DetectOnly, set LeakMonitor.Cascade and LeakMonitor.Name on one page, pop it, and read OnLeaked or OnCollected.


By Admin

Top comments (0)