Install
$ agentstack add skill-davidortinau-maui-skills-maui-custom-handlers ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
.NET MAUI Custom Handlers
Decision: Customize Existing vs. Create New
| Scenario | Approach | |---|---| | Change how a built-in control looks/behaves on one platform | Customize — use AppendToMapping / PrependToMapping | | Need the change on only some instances of a control | Customize — subclass the control + type-check in mapper | | Need a completely new cross-platform control with native backing | Create new handler with partial classes |
> ⚠️ Prefer AppendToMapping over ModifyMapping. ModifyMapping replaces > the default mapper action entirely — if the framework adds behaviour in a > future release, your override silently drops it.
Gotchas & Common Mistakes
Mapper customizations are global
Every instance of the control is affected. Guard with a subclass check for instance-specific behaviour:
// ❌ Removes borders from EVERY Entry in the app
EntryHandler.Mapper.AppendToMapping("NoBorder", (handler, view) =>
{
#if ANDROID
handler.PlatformView.Background = null;
#endif
});
// ✅ Only affects BorderlessEntry instances
EntryHandler.Mapper.AppendToMapping("NoBorder", (handler, view) =>
{
if (view is not BorderlessEntry) return;
#if ANDROID
handler.PlatformView.Background = null;
#endif
});
Unsubscribe native events in HandlerChanging
Failing to remove native event handlers causes memory leaks because the native view may outlive the managed wrapper.
// ❌ Subscribes but never unsubscribes — leaks
entry.HandlerChanged += (s, e) =>
{
#if ANDROID
((Entry)s!).Handler!.PlatformView.As()!
.FocusChange += OnNativeFocusChange;
#endif
};
// ✅ Pair subscribe in HandlerChanged with unsubscribe in HandlerChanging
entry.HandlerChanged += OnHandlerChanged;
entry.HandlerChanging += OnHandlerChanging;
Partial class name/namespace mismatch
Namespace and class name must match exactly across the shared handler file and every platform file. A mismatch silently creates separate classes — no compiler error, just a handler that does nothing on that platform.
Conditional using placement
The using PlatformView = ... aliases must be at the top of the shared handler file (not the platform files) so the ViewHandler base-class generic resolves correctly per platform.
// ✅ Top of Handlers/VideoPlayerHandler.cs
#if ANDROID
using PlatformView = Android.Widget.VideoView;
#elif IOS || MACCATALYST
using PlatformView = AVKit.AVPlayerViewController;
#elif WINDOWS
using PlatformView = Microsoft.UI.Xaml.Controls.MediaPlayerElement;
#endif
Missing CreatePlatformView()
Each platform partial must override CreatePlatformView(). Omitting it produces a compile error — but the error message points at the base class, not your handler, making it confusing to debug.
Mapper Method Selection
| Method | Risk | Use when | |---|---|---| | AppendToMapping | Low — runs after default | Adding behaviour without breaking defaults | | PrependToMapping | Low — runs before default | Setting initial state that the default can override | | ModifyMapping | ⚠️ High — replaces default | You intentionally want to suppress the framework's mapper logic |
PropertyMapper vs. CommandMapper
| Mapper | Purpose | Pattern | |---|---|---| | PropertyMapper | Sync a bindable property to the native view | Runs whenever the property value changes | | CommandMapper | Fire-and-forget action from control → handler | Runs once per invocation, no return value |
> ⚠️ Don't put property sync logic in CommandMapper — it won't re-run when > the property changes, leading to stale native views.
Checklist — New Handler
- [ ] Cross-platform control inherits
View(or appropriate base) - [ ] Shared handler file has conditional
using PlatformView = ...aliases - [ ] Handler inherits
ViewHandler - [ ]
PropertyMappermaps every bindable property - [ ] Each platform partial overrides
CreatePlatformView() - [ ] Namespace + class name identical across all partial files
- [ ] Handler registered in
MauiProgram.csviaConfigureMauiHandlers - [ ] Native event subscriptions cleaned up in
HandlerChanging
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: davidortinau
- Source: davidortinau/maui-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.