
Maui App Lifecycle
- 39 installs
- 163 repo stars
- Updated July 6, 2026
- davidortinau/maui-skills
Covers the .NET MAUI app lifecycle including the four app states and cross-platform Window lifecycle events.
About
Explains the .NET MAUI app lifecycle across the four app states (not running, running, deactivated, stopped) and cross-platform Window lifecycle events. A developer uses it when handling app state transitions and lifecycle events in a MAUI app.
- The four app states: not running, running, deactivated, stopped
- Cross-platform Window lifecycle events
Maui App Lifecycle by the numbers
- 39 all-time installs (skills.sh)
- Ranked #630 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/davidortinau/maui-skills --skill maui-app-lifecycleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 39 |
|---|---|
| repo stars | ★ 163 |
| Last updated | July 6, 2026 |
| Repository | davidortinau/maui-skills ↗ |
What it does
Covers the .NET MAUI app lifecycle including the four app states and cross-platform Window lifecycle events.
Files
.NET MAUI App Lifecycle
Critical Behavioral Gotchas
⚠️ Resumed ≠ first launch
Resumed only fires when returning from the Stopped state. On first launch the sequence is Created → Activated — Resumed is never called.
// ❌ Putting initialization logic in OnResumed — won't run on first launch
protected override void OnResumed()
{
LoadUserProfile(); // Skipped on cold start!
}
// ✅ Use OnActivated for logic that must run on every foreground entry
protected override void OnActivated()
{
LoadUserProfile(); // Runs on both first launch and resume
}⚠️ Deactivated ≠ Stopped
A dialog, split-screen, or notification pull-down triggers Deactivated without Stopped. Don't save heavy state on Deactivated — the app may never actually background.
// ❌ Heavy save on every deactivation — fires too often
protected override void OnDeactivated()
{
await SaveAllDataToDatabase(); // Wasteful for a dialog appearance
}
// ✅ Save state only when truly backgrounded
protected override void OnStopped()
{
await SaveAllDataToDatabase();
}⚠️ Android back button skips Stopped
On Android, pressing the hardware back button may call Destroying without Stopped if the activity finishes. Critical save logic in OnStopped alone can be missed.
// ✅ Save in both Stopped and Destroying for safety on Android
protected override void OnStopped()
{
base.OnStopped();
SaveDraft();
}
protected override void OnDestroying()
{
base.OnDestroying();
SaveDraft(); // Catches Android back-button finish
}⚠️ Multiple windows fire independently
On iPad, Mac Catalyst, and desktop Windows, each Window instance fires its own lifecycle events independently. Don't assume a single global lifecycle.
Performance: Keep Handlers Fast
Long-running work in lifecycle handlers causes ANR kills on Android (5s timeout) and watchdog kills on iOS (limited background time).
// ❌ Blocking the lifecycle handler
protected override void OnStopped()
{
Thread.Sleep(3000); // ANR on Android!
SaveData();
}
// ✅ Fire-and-forget or use a brief async save
protected override void OnStopped()
{
base.OnStopped();
Preferences.Set("draft_text", _viewModel.DraftText); // Fast, synchronous
}For larger state, use SecureStorage or file-based serialization — but keep it under 1–2 seconds.
iOS Scene Lifecycle
iOS 13+ uses the scene lifecycle (SceneWillConnect, etc.). Older delegate methods are still forwarded by MAUI, but you should target scene-based APIs for modern iOS.
State Preservation Checklist
- [ ] Transient UI state (scroll position, draft text) saved in
OnStopped - [ ] State restored in
OnResumed— not inOnActivated(avoid double-restore) - [ ] No heavy I/O in
OnDeactivated— it fires too frequently - [ ] Android: critical save logic also in
OnDestroying(back-button case) - [ ] Lifecycle handlers complete in under 2 seconds
- [ ] Multi-window apps handle per-window state independently
.NET MAUI App Lifecycle — API Reference
App States
A MAUI app moves through four logical states:
| State | Meaning |
|---|---|
| Not Running | App process does not exist. |
| Running | App is in the foreground and receiving input. |
| Deactivated | App is visible but lost focus (e.g. a dialog or split-screen). |
| Stopped | App is fully backgrounded; UI is not visible. |
Typical flow: Not Running → Running → Deactivated → Stopped → Running (resumed) or Not Running (terminated).
Cross-platform Window Events
Microsoft.Maui.Controls.Window exposes six lifecycle events:
| Event | When it fires |
|---|---|
Created | Window has been created (native window allocated). |
Activated | Window has been activated and is receiving input. |
Deactivated | Window lost focus but may still be visible. |
Stopped | Window is no longer visible (backgrounded). |
Resumed | Window returns to the foreground after being stopped. |
Destroying | Window is being torn down (native window deallocated). |
Subscribing to Window Events
Option A — Override CreateWindow in App
public partial class App : Application
{
protected override Window CreateWindow(IActivationState? activationState)
{
var window = base.CreateWindow(activationState);
window.Created += (s, e) => Log("Window Created");
window.Activated += (s, e) => Log("Window Activated");
window.Deactivated += (s, e) => Log("Window Deactivated");
window.Stopped += (s, e) => Log("Window Stopped");
window.Resumed += (s, e) => Log("Window Resumed");
window.Destroying += (s, e) => Log("Window Destroying");
return window;
}
}Option B — Custom Window subclass with overrides
public class AppWindow : Window
{
public AppWindow() : base() { }
public AppWindow(Page page) : base(page) { }
protected override void OnCreated() { /* init work */ }
protected override void OnActivated() { /* refresh UI */ }
protected override void OnDeactivated() { /* pause timers */ }
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)
{
return new AppWindow(new AppShell());
}Platform Lifecycle Event Mapping
Android
| Window event | Android Activity 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 |
Platform-specific Lifecycle Events
Use ConfigureLifecycleEvents in MauiProgram.cs to hook directly into native callbacks:
builder.ConfigureLifecycleEvents(events =>
{
#if ANDROID
events.AddAndroid(android => android
.OnCreate((activity, bundle) => Log("Android OnCreate"))
.OnStart(activity => Log("Android OnStart"))
.OnResume(activity => Log("Android OnResume"))
.OnPause(activity => Log("Android OnPause"))
.OnStop(activity => Log("Android OnStop"))
.OnDestroy(activity => Log("Android OnDestroy")));
#elif IOS || MACCATALYST
events.AddiOS(ios => ios
.WillFinishLaunching((app, options) => { Log("iOS WillFinishLaunching"); return true; })
.SceneWillConnect((scene, session, options) => Log("iOS SceneWillConnect"))
.DidBecomeActive(app => Log("iOS DidBecomeActive"))
.WillResignActive(app => Log("iOS WillResignActive"))
.DidEnterBackground(app => Log("iOS DidEnterBackground"))
.WillTerminate(app => Log("iOS WillTerminate")));
#elif WINDOWS
events.AddWindows(windows => windows
.OnLaunched((app, args) => Log("Windows OnLaunched"))
.OnActivated((window, args) => Log("Windows Activated"))
.OnClosed((window, args) => Log("Windows Closed")));
#endif
});State Preservation Pattern
Save and restore transient state during backgrounding:
protected override void OnStopped()
{
base.OnStopped();
Preferences.Set("draft_text", _viewModel.DraftText);
Preferences.Set("scroll_position", _viewModel.ScrollY);
}
protected override void OnResumed()
{
base.OnResumed();
_viewModel.DraftText = Preferences.Get("draft_text", string.Empty);
_viewModel.ScrollY = Preferences.Get("scroll_position", 0.0);
}For larger state, use SecureStorage or file-based serialization instead of Preferences.