maui-app-lifecycle
SKILL.md
.NET MAUI App Lifecycle
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). |
Important:
Resumednever fires on the app's first launch. It only fires when returning from theStoppedstate.
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.
Key behavioural notes
- Resumed ≠ first launch.
Resumedonly fires when returning fromStopped. On first launch the sequence isCreated→Activated. - Deactivated ≠ Stopped. A dialog or split-screen triggers
DeactivatedwithoutStopped. - Android back button may call
DestroyingwithoutStoppedif the activity finishes. - iOS scene lifecycle is used on iOS 13+; older delegate methods are still forwarded.
- Multiple windows (iPad, Mac Catalyst, desktop Windows): each
Windowinstance fires its own events independently. - Keep lifecycle handlers fast. Long-running work should use
Task.Runor background services to avoid ANR/watchdog kills.
Weekly Installs
12
Repository
davidortinau/maui-skillsGitHub Stars
71
First Seen
Feb 17, 2026
Security Audits
Installed on
opencode12
github-copilot12
codex12
kimi-cli12
gemini-cli12
amp12