$npx -y skills add managedcode/dotnet-skills --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 l
| 1 | # .NET MAUI App Lifecycle |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | ## When to Use |
| 6 | |
| 7 | - Saving or restoring state when the app backgrounds or resumes |
| 8 | - Subscribing to Window lifecycle events (Created, Activated, Deactivated, Stopped, Resumed, Destroying) |
| 9 | - Hooking into platform-native lifecycle callbacks via `ConfigureLifecycleEvents` |
| 10 | - Deciding where to place initialization, teardown, or refresh logic |
| 11 | - Understanding the difference between Deactivated and Stopped |
| 12 | |
| 13 | ## When Not to Use |
| 14 | |
| 15 | - Page-level navigation events — use Shell navigation guidance instead |
| 16 | - Registering services at startup — use dependency injection guidance instead |
| 17 | - Calling platform-specific APIs outside lifecycle context — use platform invoke guidance instead |
| 18 | |
| 19 | ## Inputs |
| 20 | |
| 21 | - The target lifecycle transition (e.g., "save draft when backgrounded", "refresh data on resume") |
| 22 | - Which platforms the developer targets (Android, iOS, Mac Catalyst, Windows) |
| 23 | - Whether the app uses multiple windows (iPad, Mac Catalyst, desktop Windows) |
| 24 | |
| 25 | ## App States |
| 26 | |
| 27 | A .NET MAUI app moves through four states: |
| 28 | |
| 29 | | State | Description | |
| 30 | |---|---| |
| 31 | | **Not Running** | Process does not exist | |
| 32 | | **Running** | Foreground, receiving input | |
| 33 | | **Deactivated** | Visible but lost focus (dialog, split-screen, notification shade) | |
| 34 | | **Stopped** | Fully backgrounded, UI not visible | |
| 35 | |
| 36 | Typical flow: Not Running → Running → Deactivated → Stopped → Running (resumed) or Not Running (terminated). |
| 37 | |
| 38 | ## Window Lifecycle Events |
| 39 | |
| 40 | `Microsoft.Maui.Controls.Window` exposes six cross-platform events: |
| 41 | |
| 42 | | Event | Fires when | |
| 43 | |---|---| |
| 44 | | `Created` | Native window allocated | |
| 45 | | `Activated` | Window receives input focus | |
| 46 | | `Deactivated` | Window loses focus (may still be visible) | |
| 47 | | `Stopped` | Window is no longer visible | |
| 48 | | `Resumed` | Window returns to foreground after Stopped | |
| 49 | | `Destroying` | Native window is being torn down | |
| 50 | |
| 51 | ### Subscribing via CreateWindow |
| 52 | |
| 53 | Override `CreateWindow` in your `App` class and attach event handlers: |
| 54 | |
| 55 | ```csharp |
| 56 | public partial class App : Application |
| 57 | { |
| 58 | protected override Window CreateWindow(IActivationState? activationState) |
| 59 | { |
| 60 | var window = base.CreateWindow(activationState); |
| 61 | |
| 62 | window.Created += (s, e) => Debug.WriteLine("Created"); |
| 63 | window.Activated += (s, e) => Debug.WriteLine("Activated"); |
| 64 | window.Deactivated += (s, e) => Debug.WriteLine("Deactivated"); |
| 65 | window.Stopped += (s, e) => Debug.WriteLine("Stopped"); |
| 66 | window.Resumed += (s, e) => Debug.WriteLine("Resumed"); |
| 67 | window.Destroying += (s, e) => Debug.WriteLine("Destroying"); |
| 68 | |
| 69 | return window; |
| 70 | } |
| 71 | } |
| 72 | ``` |
| 73 | |
| 74 | ### Subscribing via a Custom Window Subclass |
| 75 | |
| 76 | Create a `Window` subclass and override the virtual methods: |
| 77 | |
| 78 | ```csharp |
| 79 | public class AppWindow : Window |
| 80 | { |
| 81 | public AppWindow(Page page) : base(page) { } |
| 82 | |
| 83 | protected override void OnActivated() { /* refresh UI */ } |
| 84 | protected override void OnStopped() { /* save state */ } |
| 85 | protected override void OnResumed() { /* restore state */ } |
| 86 | protected override void OnDestroying() { /* cleanup */ } |
| 87 | } |
| 88 | ``` |
| 89 | |
| 90 | Return it from `CreateWindow`: |
| 91 | |
| 92 | ```csharp |
| 93 | protected override Window CreateWindow(IActivationState? activationState) |
| 94 | => new AppWindow(new AppShell()); |
| 95 | ``` |
| 96 | |
| 97 | ## Workflow: Save and Restore State on Background |
| 98 | |
| 99 | 1. **Identify transient state** — draft text, scroll position, form inputs, timer values. |
| 100 | 2. **Save in `OnStopped`** — use `Preferences` for small values or file serialization for larger state. |
| 101 | 3. **Restore in `OnResumed`** — read back saved values and apply to your view model. |
| 102 | 4. **Also save in `OnDestroying`** on Android — the back button can skip `Stopped` entirely. |
| 103 | 5. **Keep handlers fast** — complete within 1–2 seconds to avoid ANR on Android or watchdog kills on iOS. |
| 104 | |
| 105 | ```csharp |
| 106 | protected override void OnStopped() |
| 107 | { |
| 108 | base.OnStopped(); |
| 109 | Preferences.Set("draft_text", _viewModel.DraftText); |
| 110 | Preferences.Set("scroll_y", _viewModel.ScrollY); |
| 111 | } |
| 112 | |
| 113 | protected override void OnResumed() |
| 114 | { |
| 115 | base.OnResumed(); |
| 116 | _viewModel.DraftText = Preferences.Get("draft |