.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