
Skill
maui-custom-handlers
implement and migrate MAUI visual handlers
Description
Implement/migrate MAUI visual handlers. USE FOR: `EntryHandler.Mapper.AppendToMapping`, scoped `BorderlessEntry` type guards, unique mapper keys, repeated mapper callbacks, renderer-to-handler migration, `ConnectHandler`/`DisconnectHandler` cleanup, `PropertyMapper`, `CommandMapper`, custom `ViewHandler`, platform partial handlers, conflicting partial base classes, `CreatePlatformView`, native namespace leaks, `ConfigureMauiHandlers`. DO NOT USE FOR: non-visual platform APIs, full Xamarin migration, native SDK bindings, or backend code.
SKILL.md
MAUI Custom Handlers
Use this skill when a MAUI visual element needs native platform behavior. Keep handlers focused on views. Route non-visual APIs to platform services and route large native SDK surfaces to slim bindings.
Response Checklist
- Use handler language explicitly:
AppendToMapping,PropertyMapper,CommandMapper, andViewHandler. - For renderer migration, map subscription/setup to
ConnectHandlerand cleanup toDisconnectHandler. - Keep mapper changes scoped to the intended control type, not all controls.
Decision Tree
| Need | Prefer |
|---|---|
| Tweak an existing control for the whole app | EntryHandler.Mapper.AppendToMapping in MauiProgram.cs (or the matching concrete control handler mapper) |
| Tweak only specific control instances | Subclass the control and guard mapper logic with if (view is MyControl) |
| Add a reusable cross-platform control | Custom ViewHandler<TVirtualView,TPlatformView> |
| Map bindable properties to native properties | PropertyMapper entries |
| Invoke actions such as play/pause/scroll | CommandMapper entries |
| Use camera, sensors, payment, health, or background APIs | Platform service through DI |
| Wrap a third-party Android/iOS/macOS SDK | Slim binding plus a platform service facade |
Workflow
- Inspect the target frameworks and existing UI architecture.
- Define the cross-platform view API first: bindable properties, commands, and events that make sense to app code.
- Choose global mapper customization, subclass-scoped mapper customization, or a full custom handler.
- Put native implementation in partial handler files or
#ifguarded blocks for the exact platforms supported by the project. - Register handlers in
MauiProgram.cs:builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler<CameraPreview, CameraPreviewHandler>(); }); - Use
ConnectHandlerto subscribe native events and allocate native resources. - Use
DisconnectHandlerto unsubscribe, stop timers, clear delegates, and dispose only native objects owned by the handler. Callbase.DisconnectHandler(platformView)as the final statement so the base handler clears its own state. - Build each targeted platform and verify the behavior with UI automation or DevFlow when available.
Scoped Mapper Customization
For an existing MAUI control, avoid an unscoped global mapper when only one control instance should change:
public class BorderlessEntry : Entry
{
}
EntryHandler.Mapper.AppendToMapping("Borderless", (handler, view) =>
{
if (view is not BorderlessEntry)
return;
#if ANDROID
handler.PlatformView.Background = null;
#elif IOS || MACCATALYST
handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None;
#elif WINDOWS
handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness(0);
#endif
});
Use a unique mapping key. Do not repeatedly append the same mapping from page constructors; register once during app startup or from a guarded initialization path.
Custom Handler Shape
public class MeterView : View
{
public static readonly BindableProperty ValueProperty =
BindableProperty.Create(nameof(Value), typeof(double), typeof(MeterView), 0d);
public double Value
{
get => (double)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
}
public partial class MeterViewHandler
{
public static readonly IPropertyMapper<MeterView, MeterViewHandler> Mapper =
new PropertyMapper<MeterView, MeterViewHandler>(ViewHandler.ViewMapper)
{
[nameof(MeterView.Value)] = MapValue
};
public MeterViewHandler() : base(Mapper)
{
}
public static partial void MapValue(MeterViewHandler handler, MeterView view);
}
Implement CreatePlatformView, ConnectHandler, DisconnectHandler, and
mapping partials per platform. Put the ViewHandler<TVirtualView, TPlatformView>
base class on the platform partial so native types stay out of shared files:
// Platforms/Android/MeterViewHandler.android.cs
public partial class MeterViewHandler : ViewHandler<MeterView, Android.Widget.FrameLayout>
{
protected override Android.Widget.FrameLayout CreatePlatformView() => new(Context);
public static partial void MapValue(MeterViewHandler handler, MeterView view)
{
// Update handler.PlatformView from view.Value.
}
}
Property and Command Mappers
- Use
PropertyMapperfor state that should update when a bindable property changes. - Use
CommandMapperfor imperative requests such asPlay,Pause,ScrollTo, orReload. - Mapper methods should be idempotent. They may be called more than once.
- Validate
handler.PlatformViewandhandler.VirtualViewassumptions through types, not broad try/catch blocks.
Renderer Migration Notes
When replacing Xamarin.Forms renderers:
- Move renderer logic that creates native controls into
CreatePlatformView. - Move
OnElementChangedsubscriptions intoConnectHandler. - Move renderer cleanup into
DisconnectHandler, and end withbase.DisconnectHandler(platformView). - Replace
OnElementPropertyChangedswitch statements withPropertyMapperentries. - Replace renderer actions with
CommandMapperentries or view methods that callHandler?.Invoke.
Validation Checklist
- The handler is registered in one startup location.
- Mapper keys are unique and scoped when customization should not be global.
- Native event subscriptions are unsubscribed in
DisconnectHandler. - No platform namespace leaks into shared code unintentionally.
- The implementation builds for every target framework it is included in.
- Non-visual SDK work is not hidden in a handler.
More skills from the maui-labs repository
View all 32 skillsandroid-slim-bindings
create Android slim bindings for .NET
Jul 12.NETAndroidJavaKotlin +1devflow-automation
automate .NET MAUI app state
Jul 12.NETAutomationMAUIMobiledevflow-connect
diagnose DevFlow agent connectivity issues
Jul 12DebuggingMAUINetworkingdotnet-workload-info
discover MAUI workload metadata
Jul 12C#CLIEngineeringMAUIios-slim-bindings
create iOS slim bindings for MAUI
Jul 12.NETiOSMAUISwift +1maui-accessibility
implement accessibility in .NET MAUI apps
Jul 12.NETAccessibilityMAUIMobile +1
More from .NET (Microsoft)
View publishermultithreaded-task-migration
migrate MSBuild tasks to multithreaded mode
msbuild
Jul 22.NETEngineeringPerformanceanalyzing-dotnet-performance
analyze .NET code for performance anti-patterns
skills
Jul 12.NETCode AnalysisDebuggingPerformanceandroid-tombstone-symbolication
symbolicate .NET runtime frames in Android tombstones
skills
Jul 12.NETAndroidDebuggingMicrosoftapple-crash-symbolication
symbolicate .NET runtime frames in crash logs
skills
Jul 12.NETDebuggingiOSmacOS +1assertion-quality
evaluate assertion quality in test suites
skills
Jul 12Code AnalysisQATestingauthor-component
create and review Blazor components
skills
Jul 15.NETBlazorC#UI Components +1