npx claudepluginhub dotnet/skills --plugin dotnet-mauiThis skill uses the workspace's default tool permissions.
Handle application state transitions correctly in .NET MAUI. This skill covers the cross-platform Window lifecycle events, their platform-native mappings, and patterns for preserving state across backgrounding and resume cycles.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
Handle application state transitions correctly in .NET MAUI. This skill covers the cross-platform Window lifecycle events, their platform-native mappings, and patterns for preserving state across backgrounding and resume cycles.
ConfigureLifecycleEventsA .NET MAUI app moves through four states:
| State | Description |
|---|---|
| Not Running | Process does not exist |
| Running | Foreground, receiving input |
| Deactivated | Visible but lost focus (dialog, split-screen, notification shade) |
| Stopped | Fully backgrounded, UI not visible |
Typical flow: Not Running → Running → Deactivated → Stopped → Running (resumed) or Not Running (terminated).
Microsoft.Maui.Controls.Window exposes six cross-platform events:
| Event | Fires when |
|---|---|
Created | Native window allocated |
Activated | Window receives input focus |
Deactivated | Window loses focus (may still be visible) |
Stopped | Window is no longer visible |
Resumed | Window returns to foreground after Stopped |
Destroying | Native window is being torn down |
Override CreateWindow in your App class and attach event handlers:
public partial class App : Application
{
protected override Window CreateWindow(IActivationState? activationState)
{
var window = base.CreateWindow(activationState);
window.Created += (s, e) => Debug.WriteLine("Created");
window.Activated += (s, e) => Debug.WriteLine("Activated");
window.Deactivated += (s, e) => Debug.WriteLine("Deactivated");
window.Stopped += (s, e) => Debug.WriteLine("Stopped");
window.Resumed += (s, e) => Debug.WriteLine("Resumed");
window.Destroying += (s, e) => Debug.WriteLine("Destroying");
return window;
}
}
Create a Window subclass and override the virtual methods:
public class AppWindow : Window
{
public AppWindow(Page page) : base(page) { }
protected override void OnActivated() { /* refresh UI */ }
protected override void OnStopped() { /* save state */ }
protected override void OnResumed() { /* restore state */ }
protected override void OnDestroying() { /* cleanup */ }
}
Return it from CreateWindow:
protected override Window CreateWindow(IActivationState? activationState)
=> new AppWindow(new AppShell());
OnStopped — use Preferences for small values or file serialization for larger state.OnResumed — read back saved values and apply to your view model.OnDestroying on Android — the back button can skip Stopped entirely.protected override void OnStopped()
{
base.OnStopped();
Preferences.Set("draft_text", _viewModel.DraftText);
Preferences.Set("scroll_y", _viewModel.ScrollY);
}
protected override void OnResumed()
{
base.OnResumed();
_viewModel.DraftText = Preferences.Get("draft_text", string.Empty);
_viewModel.ScrollY = Preferences.Get("scroll_y", 0.0);
}
protected override void OnDestroying()
{
base.OnDestroying();
// Android back-button can skip Stopped
Preferences.Set("draft_text", _viewModel.DraftText);
}
| Window Event | Android Callback |
|---|---|
| Created | OnCreate |
| Activated | OnResume |
| Deactivated | OnPause |
| Stopped | OnStop |
| Resumed | OnRestart → OnStart → OnResume |
| Destroying | OnDestroy |
| Window Event | UIKit Callback |
|---|---|
| Created | WillFinishLaunching / SceneWillConnect |
| Activated | DidBecomeActive |
| Deactivated | WillResignActive |
| Stopped | DidEnterBackground |
| Resumed | WillEnterForeground |
| Destroying | WillTerminate |
| Window Event | WinUI Callback |
|---|---|
| Created | OnLaunched |
| Activated | Activated (foreground) |
| Deactivated | Activated (background) |
| Stopped | VisibilityChanged (false) |
| Resumed | VisibilityChanged (true) |
| Destroying | Closed |
Use ConfigureLifecycleEvents in MauiProgram.cs when you need platform-specific callbacks beyond what Window events provide:
builder.ConfigureLifecycleEvents(events =>
{
#if ANDROID
events.AddAndroid(android => android
.OnCreate((activity, bundle) => Debug.WriteLine("Android OnCreate"))
.OnResume(activity => Debug.WriteLine("Android OnResume"))
.OnPause(activity => Debug.WriteLine("Android OnPause"))
.OnStop(activity => Debug.WriteLine("Android OnStop"))
.OnDestroy(activity => Debug.WriteLine("Android OnDestroy")));
#elif IOS || MACCATALYST
events.AddiOS(ios => ios
.DidBecomeActive(app => Debug.WriteLine("iOS DidBecomeActive"))
.WillResignActive(app => Debug.WriteLine("iOS WillResignActive"))
.DidEnterBackground(app => Debug.WriteLine("iOS DidEnterBackground"))
.WillEnterForeground(app => Debug.WriteLine("iOS WillEnterForeground")));
#elif WINDOWS
events.AddWindows(windows => windows
.OnLaunched((app, args) => Debug.WriteLine("Windows OnLaunched"))
.OnActivated((window, args) => Debug.WriteLine("Windows Activated"))
.OnClosed((window, args) => Debug.WriteLine("Windows Closed")));
#endif
});
Resumed does not fire on first launch. The initial sequence is Created → Activated. Use OnActivated for logic that must run on every foreground entry, not OnResumed.
Deactivated ≠ Stopped. A dialog, split-screen, or notification pull-down triggers Deactivated without Stopped. Do not perform heavy saves in OnDeactivated — the app may never actually background.
Android back button skips Stopped. On Android, pressing back may call Destroying directly without Stopped. Place critical save logic in both OnStopped and OnDestroying.
Multi-window apps fire events independently. On iPad, Mac Catalyst, and desktop Windows each Window instance fires its own lifecycle events. Do not assume a single global lifecycle.
Long-running handlers cause kills. Android enforces a ~5 second ANR timeout; iOS has limited background execution time. Keep lifecycle handlers synchronous and fast — use Preferences for quick saves, not database writes.
Do not use legacy Xamarin.Forms lifecycle methods. Application.OnStart(), Application.OnSleep(), and Application.OnResume() exist for backward compatibility but bypass Window-level events. In .NET MAUI, prefer Window lifecycle events (OnActivated, OnStopped, OnResumed, etc.) for correct multi-window behavior.