In Flutter, what are the five AppLifecycleState values, and what does each one mean on Android and iOS?
answer
- one state machine, all platforms
- detached is the initial value
- inactive: visible but unfocused
- hidden synthesized on phones
- paused: no onBeginFrame, onDrawFrame
basics
~10 sAppLifecycleState has resumed (visible, focused), inactive (visible, unfocused), hidden (no view visible), paused (not visible, no frames, mobile only) and detached (engine without a view, also the initial state).
solid answer
~40 s`AppLifecycleState` is one state machine shared by every platform. `resumed` means visible with input focus. `inactive` means still visible but unfocused: on iOS a phone call or the app switcher, on Android `Activity.onPause` or a lost window focus. `hidden` means no view is visible; on phones it is synthesized between `inactive` and `paused`, and on desktop and the web it is where a minimized or hidden app stays. `paused` exists only on iOS and Android: the app is not visible and the engine stops scheduling frames. `detached` is the initial value and the state of an engine with no view. The framework delivers each intermediate state in order, but a killed process gets no notification at all.
code
dart · 14 linesimport 'package:flutter/widgets.dart';
String describe(AppLifecycleState state) => switch (state) {
AppLifecycleState.resumed => 'visible and focused',
AppLifecycleState.inactive => 'visible, input focus lost',
AppLifecycleState.hidden => 'no view visible',
AppLifecycleState.paused => 'backgrounded, no frames (iOS and Android)',
AppLifecycleState.detached => 'engine running without a view',
};
void logCurrentState() {
final AppLifecycleState? current = WidgetsBinding.instance.lifecycleState;
debugPrint(current == null ? 'no lifecycle message yet' : describe(current));
}go deeper
Recall the five names and which one means the app is fully in use. Know that inactive still means visible.
Explain how Android's onPause and onStop map to inactive and paused, why hidden is synthesized on phones, and that paused stops frame callbacks.
Show that you save state early because kills send no notification, and that you pick hidden rather than paused for code that must also run on desktop and the web.
Frame the enum as a lowest-common-denominator contract: argue which work belongs on visibility versus focus across a multi-platform codebase, and where platform code must take over.
## The state machine behind `AppLifecycleState` **`AppLifecycleState`** is an enum in `dart:ui` that tells a Flutter app how visible and how interactive it currently is. Flutter models every platform with **one shared state machine**, so the same five values appear on Android, iOS, desktop and the web. Where a platform has no native equivalent for a state, the framework **synthesizes** it so that code written against the enum behaves the same everywhere. The initial value is **`detached`**: before the first lifecycle message arrives from the embedder, the app is considered to have no view. As soon as the platform reports its real state (usually `resumed`), the value moves on. You can read the current value at any time from `WidgetsBinding.instance.lifecycleState`, which is nullable (`AppLifecycleState?`) because it has no value until the first lifecycle message has been handled. ## The five values | Value | Meaning | Android | iOS | |---|---|---|---| | `resumed` | Visible and has input focus | Activity resumed and its window focused | Foreground active | | `inactive` | At least one view visible, none focused | `Activity.onPause`, or resumed but the window lost focus (split screen, system dialog, notification shade) | Foreground inactive: phone call, Touch ID prompt, app switcher, Control Center | | `hidden` | No view is visible | Synthesized between `inactive` and `paused` | Synthesized between `inactive` and `paused` | | `paused` | Not visible, not responding to input; no frames | `Activity.onStop` | Background | | `detached` | Engine running without any host view | All views detached | All views detached | Key points to remember: - **`inactive` is not "in the background".** The app is still on screen; it has only lost input focus. Apps in this state should assume they may be hidden and paused at any moment. - **`paused` stops frame production.** While paused, the engine does not call `PlatformDispatcher.onBeginFrame` or `onDrawFrame`, so nothing is built or painted. - **`paused` exists only on iOS and Android.** A minimized desktop window or a hidden browser tab reaches `hidden` and stays there. - **`hidden` is the portable "not visible" hook.** It was added in Flutter 3.13 so that one handler can react to "the user cannot see me" on every platform. ## How transitions are delivered The framework never jumps across states. In `ServicesBinding`, an incoming platform message is expanded into the chain of intermediate states, and each one is delivered in turn. Going from `resumed` to the background on a phone therefore produces, in order: 1. `inactive` (focus lost), 2. `hidden` (synthesized), 3. `paused` (no longer visible). Coming back runs the chain in reverse: `hidden`, `inactive`, then `resumed`. The chain follows the enum's own order, `detached`, `resumed`, `inactive`, `hidden`, `paused`, and a move to `detached` from a running state first passes through every later state. Listeners that switch on the value can therefore rely on seeing every intermediate step rather than a single jump. ## What you cannot rely on The `AppLifecycleState` documentation is explicit that an app should **not** rely on receiving every notification. If the process is killed from a task manager, by a signal, or because the device loses power, no notification is sent and some states are skipped entirely. In practice this means: - save anything the user would miss **early**, when the app becomes `inactive` or `hidden`, not in a `detached` handler; - treat `detached` as best-effort cleanup, not a guaranteed shutdown hook; - release scarce hardware (camera, microphone, sensors) no later than `inactive`, because the OS may reclaim it while the app is still technically visible. ## Version note Before Flutter 3.13 the enum had four values. The 3.13 migration guide asks code that switches exhaustively over `AppLifecycleState` to add a `hidden` case; in Dart 3 a `switch` over the enum without a `default` and without that case no longer compiles. Code with a `default` branch compiles unchanged but may treat `hidden` wrongly, so every `default` deserves a second look.
- Can a Flutter app count on reaching detached before its process is killed?No. The `AppLifecycleState` docs say apps must not rely on receiving every notification: a task-manager kill, a signal or a power loss sends nothing, and states may be skipped. Save user work when the app becomes `inactive` or `hidden`, and treat `detached` as best-effort cleanup only.
- Why does a minimized Flutter desktop app never report paused?`paused` is only entered on iOS and Android. Desktop and web apps that lose visibility move to `hidden` and stay there, which is why `hidden` is the portable place to stop visible-only work such as animations or outgoing video.
Think of a shop. resumed is open with the clerk serving; inactive is the door open but the clerk on the phone; hidden is the blinds pulled down; paused is the lights off; detached is the building standing with no storefront. A power cut closes the shop without any of those signs going up.
saying these in an interview costs you the question
- inactive means the app has gone to the background.
- hidden replaced paused, so phones never report paused anymore.
- detached is a reliable shutdown hook for saving data.
- A minimized desktop window reports paused just like a phone.
- A switch over AppLifecycleState only needs four cases.