Agent skill
maui-app-lifecycle
.NET MAUI app lifecycle guidance — the four app states, cross-platform Window lifecycle events (Created, Activated, Deactivated, Stopped, Resumed, Destroying), platform-specific lifecycle mapping, backgrounding and resume behavior, and state-preservation patterns. USE FOR: "app lifecycle", "window lifecycle events", "save state on background", "resume app", "OnStopped", "OnResumed", "backgrounding", "deactivated event", "ConfigureLifecycleEvents", "platform lifecycle hooks". DO NOT USE FOR: navigation events (use maui-shell-navigation), dependency injection setup (use maui-dependency-injection), platform API invocation (use conditional compilation and partial classes).
Install this agent skill to your Project
npx add-skill https://github.com/managedcode/dotnet-skills/tree/main/external-sources/upstreams/dotnet-skills/dotnet-maui/skills/maui-app-lifecycle
SKILL.md
.NET MAUI App Lifecycle
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.
When to Use
- Saving or restoring state when the app backgrounds or resumes
- Subscribing to Window lifecycle events (Created, Activated, Deactivated, Stopped, Resumed, Destroying)
- Hooking into platform-native lifecycle callbacks via
ConfigureLifecycleEvents - Deciding where to place initialization, teardown, or refresh logic
- Understanding the difference between Deactivated and Stopped
When Not to Use
- Page-level navigation events — use Shell navigation guidance instead
- Registering services at startup — use dependency injection guidance instead
- Calling platform-specific APIs outside lifecycle context — use platform invoke guidance instead
Inputs
- The target lifecycle transition (e.g., "save draft when backgrounded", "refresh data on resume")
- Which platforms the developer targets (Android, iOS, Mac Catalyst, Windows)
- Whether the app uses multiple windows (iPad, Mac Catalyst, desktop Windows)
App States
A .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).
Window Lifecycle Events
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 |
Subscribing via CreateWindow
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;
}
}
Subscribing via a Custom Window Subclass
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());
Workflow: Save and Restore State on Background
- Identify transient state — draft text, scroll position, form inputs, timer values.
- Save in
OnStopped— usePreferencesfor small values or file serialization for larger state. - Restore in
OnResumed— read back saved values and apply to your view model. - Also save in
OnDestroyingon Android — the back button can skipStoppedentirely. - Keep handlers fast — complete within 1–2 seconds to avoid ANR on Android or watchdog kills on iOS.
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);
}
Platform Lifecycle Mapping
Android
| Window Event | Android Callback |
|---|---|
| Created | OnCreate |
| Activated | OnResume |
| Deactivated | OnPause |
| Stopped | OnStop |
| Resumed | OnRestart → OnStart → OnResume |
| Destroying | OnDestroy |
iOS / Mac Catalyst
| Window Event | UIKit Callback |
|---|---|
| Created | WillFinishLaunching / SceneWillConnect |
| Activated | DidBecomeActive |
| Deactivated | WillResignActive |
| Stopped | DidEnterBackground |
| Resumed | WillEnterForeground |
| Destroying | WillTerminate |
Windows (WinUI)
| Window Event | WinUI Callback |
|---|---|
| Created | OnLaunched |
| Activated | Activated (foreground) |
| Deactivated | Activated (background) |
| Stopped | VisibilityChanged (false) |
| Resumed | VisibilityChanged (true) |
| Destroying | Closed |
Hooking Native Lifecycle Directly
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
});
Common Pitfalls
-
Resumed does not fire on first launch. The initial sequence is
Created→Activated. UseOnActivatedfor logic that must run on every foreground entry, notOnResumed. -
Deactivated ≠ Stopped. A dialog, split-screen, or notification pull-down triggers
DeactivatedwithoutStopped. Do not perform heavy saves inOnDeactivated— the app may never actually background. -
Android back button skips Stopped. On Android, pressing back may call
Destroyingdirectly withoutStopped. Place critical save logic in bothOnStoppedandOnDestroying. -
Multi-window apps fire events independently. On iPad, Mac Catalyst, and desktop Windows each
Windowinstance 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
Preferencesfor quick saves, not database writes. -
Do not use legacy Xamarin.Forms lifecycle methods.
Application.OnStart(),Application.OnSleep(), andApplication.OnResume()exist for backward compatibility but bypass Window-level events. In .NET MAUI, preferWindowlifecycle events (OnActivated,OnStopped,OnResumed, etc.) for correct multi-window behavior.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
dotnet-project-setup
Create or reorganize .NET solutions with clean project boundaries, repeatable SDK settings, and a maintainable baseline for libraries, apps, tests, CI, and local development.
csharp-scripts
Run single-file C# programs as scripts (file-based apps) for quick experimentation, prototyping, and concept testing. Use when the user wants to write and execute a small C# program without creating a full project.
dotnet-pinvoke
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime, SafeHandle, and cross-platform patterns. USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging existing native interop code, wrapping a C or C++ library for use in .NET, diagnosing crashes, memory leaks, or corruption at the managed/native boundary. DO NOT USE FOR: COM interop, C++/CLI mixed-mode assemblies, or pure managed code with no native dependencies.
nuget-trusted-publishing
Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish, migrate from NuGet API key, NuGet/login, secure NuGet publishing. DO NOT USE FOR: publishing to private feeds or Azure Artifacts (OIDC is nuget.org only). INVOKES: shell (powershell or bash), edit, create, ask_user for guided repo setup.
dotnet-legacy-aspnet
Maintain classic ASP.NET applications on .NET Framework, including Web Forms, older MVC, and legacy hosting patterns, while planning realistic modernization boundaries.
dotnet-code-review
Review .NET changes for bugs, regressions, architectural drift, missing tests, incorrect async or disposal behavior, and platform-specific pitfalls before you approve or merge them.
Didn't find tool you were looking for?