Skip to content

Custom handlers: document HandlerChanging unsubscribe pattern to prevent memory leaks #3253

@PureWeen

Description

@PureWeen

Summary

The custom handler documentation shows how to subscribe to native events via HandlerChanged, but does not clearly document the mandatory HandlerChanging unsubscribe pattern needed to prevent memory leaks — especially in scenarios where controls are reused (e.g., inside CollectionView or ListView DataTemplates).

Why it matters

When a handler subscribes to native platform events (e.g., Android Click, iOS TouchUpInside), the native event handler holds a reference to the managed handler. If the handler is replaced (recycling in a list) or the page is popped, the old native view may not be collected because the event subscription still holds a reference chain.

This leak is silent and cumulative — it does not crash, but memory grows steadily in list-heavy UIs until the app is killed by the OS.

What should be documented

Show the full subscribe/unsubscribe lifecycle pattern:

// Subscribe to native events when the handler connects
myControl.HandlerChanged += OnHandlerChanged;
myControl.HandlerChanging += OnHandlerChanging;

void OnHandlerChanged(object? sender, EventArgs e)
{
    if (sender is Entry entry && entry.Handler?.PlatformView is Android.Widget.EditText editText)
    {
        editText.FocusChange += OnNativeFocusChange;
    }
}

// Unsubscribe when the handler is about to change or disconnect
void OnHandlerChanging(object? sender, HandlerChangingEventArgs e)
{
    if (e.OldHandler?.PlatformView is Android.Widget.EditText editText)
    {
        editText.FocusChange -= OnNativeFocusChange;
    }
}

Important: Always unsubscribe in HandlerChanging to prevent memory leaks. Failing to remove native event handlers causes the native view to outlive the managed control, especially in recycled list items.

Suggested location

docs/user-interface/handlers/create.md — in the "Connect to native events" or lifecycle section

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions