# App Lifecycle

> Mobile app lifecycle across iOS and Android (foreground, background, suspended, killed), state restoration, process death, and cross-platform parallels in Flutter and React Native. Use when handling background work, persistence of in-flight state, or resume flows.

- Skill: `almasumdev/app-lifecycle` (Agent Skill)
- Install (CLI): `npx skillmds@latest add almasumdev/app-lifecycle`
- Raw SKILL.md: https://api.skillmd.com/api/skills/almasumdev/app-lifecycle/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: almasumdev (https://skillmd.com/u/almasumdev)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/almasumdev/app-lifecycle

---


# App Lifecycle

## Instructions

Mobile apps run in a hostile runtime. The OS freezes, backgrounds, and kills processes to preserve battery and memory. Treating the lifecycle as an afterthought leads to lost data, broken deep links, and angry reviews.

### 1. The Lifecycle States

Both platforms converge to roughly:

| State | iOS term | Android term | Cross-platform term |
| --- | --- | --- | --- |
| Visible & interactive | Active | Resumed | Foreground |
| Visible but not active | Inactive (incoming call, control center) | Paused | Inactive |
| Not visible | Background | Stopped | Background |
| Frozen but in memory | Suspended | Stopped | Suspended |
| Gone | Terminated | Process death | Killed |

### 2. Transitions and Contracts

- **Entering background**: cancel network calls not needed offline, persist drafts, release camera/audio/sensors, pause timers.
- **Entering foreground**: refresh data that may be stale, reconnect websockets, re-check auth, reclaim resources.
- **Process death**: on cold start, detect whether this is a restart from death (saved-state bundle present) or a first launch; restore the previous screen and selection.
- **Low memory warning**: drop caches, close non-visible images, release heavy objects.

### 3. Platform Hooks

```swift
// Swift / SwiftUI
@main struct App: App {
    @Environment(\.scenePhase) private var scenePhase
    var body: some Scene {
        WindowGroup { RootView() }
            .onChange(of: scenePhase) { _, phase in
                switch phase {
                case .active:     AppEvents.foreground()
                case .inactive:   AppEvents.inactive()
                case .background: AppEvents.background()
                @unknown default: break
                }
            }
    }
}
```

```kotlin
// Kotlin / Android (Lifecycle-aware observer)
class AppLifecycleObserver : DefaultLifecycleObserver {
    override fun onStart(owner: LifecycleOwner) = AppEvents.foreground()
    override fun onStop(owner: LifecycleOwner)  = AppEvents.background()
}
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
```

```dart
// Flutter
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); }
  @override void dispose()   { WidgetsBinding.instance.removeObserver(this); super.dispose(); }
  @override void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:  AppEvents.foreground();
      case AppLifecycleState.inactive: AppEvents.inactive();
      case AppLifecycleState.paused:   AppEvents.background();
      case AppLifecycleState.detached: AppEvents.detached();
      case AppLifecycleState.hidden:   break;
    }
  }
}
```

```tsx
// React Native
import { AppState } from 'react-native';
useEffect(() => {
  const sub = AppState.addEventListener('change', (s) => {
    if (s === 'active') AppEvents.foreground();
    else if (s === 'background') AppEvents.background();
  });
  return () => sub.remove();
}, []);
```

### 4. State Restoration

State restoration keeps the user's mental context after the OS kills the process.

- **iOS**: `@SceneStorage`, `StateRestoration` with `NSUserActivity` for deep links.
- **Android**: `SavedStateHandle` in ViewModels; `onSaveInstanceState` for Views; Navigation Component restores the back stack.
- **Flutter**: `RestorationMixin`, `RestorableProperty`, `restorationScopeId` in `MaterialApp`.
- **React Native**: `@react-navigation` persistence with `AsyncStorage`.

Keep restored state **small** (IDs, not payloads). Refetch the data using the ID on resume.

### 5. Background Work Budgets

The OS is strict. Do not run background network or CPU work without a matching API:

- **iOS**: `BGTaskScheduler` (background refresh, processing tasks), `URLSession` background configuration for downloads, VoIP push for real-time.
- **Android**: `WorkManager` for deferrable guaranteed work, `ForegroundService` for user-visible ongoing work (with the right foreground-service type declared), `JobScheduler` under the hood.
- **Flutter**: `workmanager` plugin, native channel on iOS.
- **React Native**: `expo-background-fetch`, `react-native-background-fetch`, Headless JS on Android.

Assume background execution may never happen. Design for eventual consistency and sync on foreground.

### 6. Process Death and Cold Start

- Restore navigation first, then restore feature state.
- Re-run auth checks and token refresh on every cold start.
- If a push notification launched the app, route to the target screen only after auth is verified.
- Measure cold start time; store and foreground launches should be under 2 seconds to first meaningful paint.

### 7. Common Bugs

- Losing form drafts when the user switches apps. Fix: persist on `onPause` / `background`.
- Continuing location updates when backgrounded without user awareness. Fix: stop on background unless there is a declared background mode and a UI affordance.
- Showing stale data after a long background. Fix: soft-invalidate caches on foreground and refetch.
- Keeping websockets open while backgrounded. Fix: close on background, reconnect with backoff on foreground, and rely on push for real-time.
- Deep link opens on cold start before the nav graph is ready. Fix: buffer intents until the navigator is mounted.

### 8. Observability

Log these events (without PII):

- `app.foreground`, `app.background`, `app.cold_start`, `app.warm_start`.
- `restore.success` / `restore.failure` with a bucketed reason.
- Cold-start duration and first-frame-time as metrics, not logs.

### 9. Anti-Patterns

- Running long work in `onPause` or `applicationWillResignActive`. You have milliseconds.
- Storing large objects in saved-state bundles. Store IDs, rehydrate.
- Polling while backgrounded. Use push or periodic `WorkManager`/`BGTask`.
- Assuming the app was foreground since last launch. Always check.

## Checklist

- [ ] Foreground/background transitions have explicit handlers.
- [ ] Drafts and in-flight forms persist on background.
- [ ] State restoration stores small keys and rehydrates data.
- [ ] Background work uses the platform-sanctioned API with correct declarations.
- [ ] Cold-start routing waits for auth and navigator readiness.
- [ ] Websockets and media resources are released on background.
- [ ] Cold-start and warm-start times are measured and budgeted.
- [ ] App handles low-memory warnings by dropping caches.

