Skip to content
NLNuvyntra Labs

Install

dotnet add package Plugin.Maui.LeakAnalyser

Package ID: Plugin.Maui.LeakAnalyser. Registration is required for leak callbacks and global teardown defaults.

Debug: detect only

using Plugin.Maui.LeakAnalyser;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder.UseMauiApp<App>();

#if DEBUG
        builder.Logging.AddDebug();
        builder.UseLeakAnalyser(options =>
        {
            options.OnLeaked = target => { /* alert / counter */ };
            options.DefaultTearDownStrategy = TearDownStrategy.DetectOnly;
        });
#endif

        return builder.Build();
    }
}
<ContentPage xmlns:la="clr-namespace:Plugin.Maui.LeakAnalyser;assembly=Plugin.Maui.LeakAnalyser"
             la:LeakMonitor.Cascade="True"
             la:LeakMonitor.Name="OrdersPage">

Debug: detect + disconnect handlers

la:LeakMonitor.Cascade="True"
la:TearDown.Cascade="True"
la:TearDown.Strategy="DisconnectHandlers"

Put TearDown after LeakMonitor. DisconnectHandlers is the default DefaultTearDownStrategy when you omit Strategy.

Production: teardown without detection

Detection forces GC and is not for Release. Teardown can stay on:

la:TearDown.Cascade="True"

Do not call UseLeakAnalyser or set LeakMonitor.Cascade in Release. Cached pages and tabs still need Suppress.

GC knobs

builder.UseLeakAnalyser(options =>
{
    options.DefaultTearDownStrategy = TearDownStrategy.DisconnectHandlers;
    options.MaxCollections = 10;
    options.MillisecondsBetweenCollections = 200;
});

Opt into Compartmentalize per page when DisconnectHandlers is not enough:

la:TearDown.Strategy="Compartmentalize"

Compose with Diagnostics

LeakAnalyser is standalone. It does not reference Diagnostics or Observability. Wire callbacks yourself when the host already uses those plugins. Treat a leaked view as a breadcrumb, not a crash.

using Plugin.Maui.Diagnostics;
using Plugin.Maui.LeakAnalyser;

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

    options.OnCollected = target =>
        MauiDiagnostics.TrackEvent($"Collected:{target.Name}");
});
#endif

Register Diagnostics first if you want its logger and timeline ready before the first leak callback. Do not call TrackException for every collected target — collection is success. Use OnLeaked only for errors.

Compose with Observability

Observability does not import leak events automatically. If the host already calls UseMauiObservability, keep LeakAnalyser registered beside it and forward OnLeaked into Diagnostics. Observability will pick up those breadcrumbs on the next report export if Diagnostics is part of that pipeline.

builder
    .UseMauiApp<App>()
    .UseMauiObservability();

#if DEBUG
builder.UseLeakAnalyser(options =>
{
    options.OnLeaked = target =>
        MauiDiagnostics.TrackEvent($"Leak:{target.Name}");
});
#endif

What not to wire

SignalWhy not
Forced GC counts as healthGC.Collect is a Debug probe, not a production metric.
Every OnCollected as an errorCollection is the healthy outcome.
Release LeakMonitor.CascadeExpensive, and can hide real leaks behind GC noise.
Package reference LeakAnalyser → DiagnosticsKeeps this plugin usable without the telemetry suite.