skip to content

Ephemeral vs App State

Ephemeral state lives in one widget's State behind setState; shared app state is lifted to a common ancestor and handed down with callbacks. Interviewers probe when that drilling stops scaling.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Flutter, what distinguishes ephemeral state from app state, and where should a password field's show/hide toggle live?

level: juniorimportance: must knowfreq 68%

answer

  1. who else needs it
  2. does it outlive the screen or session
  3. one widget's State and setState
  4. login info, preferences, carts
  5. no clear-cut rule

basics

~20 s

Ephemeral state belongs to one widget — a visibility toggle, the selected tab — and lives in its State behind setState. App state is shared across screens or kept between sessions, like the signed-in user. The password toggle is ephemeral.

solid answer

~50 s

Flutter's docs split the state you manage into **ephemeral** state — data neatly contained in one widget that nothing else reads, that needs no serialising and doesn't change in complex ways — and **app state**, which many parts of the app share or which must survive between sessions: preferences, login info, a cart. A password field's show/hide toggle is ephemeral: a `bool` in the field's own `State`, flipped with `setState` from a suffix `IconButton` and passed to `TextField(obscureText: …)`. Nobody else reads it, and resetting it when the screen closes is fine. In a notes app, the signed-in user is app state: the header, the notes list and settings all read it, and it must survive a restart. The docs stress there is no clear-cut rule — small apps can keep everything in `State`, and a piece can move from one kind to the other as requirements grow.

code

dart · 29 lines
dart
import 'package:flutter/material.dart';

class PasswordField extends StatefulWidget {
  const PasswordField({super.key, required this.controller});

  final TextEditingController controller;

  @override
  State<PasswordField> createState() => _PasswordFieldState();
}

class _PasswordFieldState extends State<PasswordField> {
  bool _obscured = true; // ephemeral: nobody else needs it

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: widget.controller,
      obscureText: _obscured,
      decoration: InputDecoration(
        labelText: 'Password',
        suffixIcon: IconButton(
          icon: Icon(_obscured ? Icons.visibility : Icons.visibility_off),
          onPressed: () => setState(() => _obscured = !_obscured),
        ),
      ),
    );
  }
}

go deeper

for a junior

Recall the two kinds with examples: toggles, tabs and animation progress are ephemeral; login info, preferences and carts are app state.

for a middle

Classify real pieces of a feature by who reads them, who changes them and how long they must live, and explain why the docs say there is no clear-cut rule.

for a senior

Spot state that sits at the wrong level — shared data hidden in one screen, or trivial toggles pushed into global stores — and plan its migration.

for a principal

Set team guidance for classifying state so features stay consistent, and decide when restoration, persistence or a shared store is worth its cost.

## What "state" means here In the broadest sense, an app's state is everything in memory while it runs — assets, textures, fonts, the framework's own bookkeeping. Flutter's docs narrow this to something useful for design: **state is whatever data you need to rebuild your UI at any moment**. Some of that the framework manages for you. The part you manage yourself falls into two conceptual kinds. ## Ephemeral state **Ephemeral state** (also called UI state or local state) is state you can neatly contain in a **single widget**. The docs' examples: - the current page of a `PageView`; - the progress of a complex animation; - the selected tab of a `BottomNavigationBar`. Other parts of the tree seldom need it, there is no need to serialise it, and it does not change in complex ways. A `StatefulWidget` with a field in its `State`, updated through `setState`, is all it needs. The notes app's password field is the textbook case: ```dart class _PasswordFieldState extends State<PasswordField> { bool _obscured = true; @override Widget build(BuildContext context) { return TextField( obscureText: _obscured, decoration: InputDecoration( labelText: 'Password', suffixIcon: IconButton( icon: Icon(_obscured ? Icons.visibility : Icons.visibility_off), onPressed: () => setState(() => _obscured = !_obscured), ), ), ); } } ``` No other widget cares whether the password is visible, and if the user leaves the sign-in screen, forgetting the choice is correct. ## App state **App state** (or shared state) is state that is not ephemeral: you want to **share it across many parts of the app** and often **keep it between sessions**. The docs list user preferences, login info, notifications, a shopping cart and read/unread flags. In the notes app: - the **signed-in user** — shown in the header, used to load notes, needed by settings; - the **notes list** — shown on the list screen, edited on the detail screen; - the **theme preference** — read by the root `MaterialApp`, saved on disk. App state is lifted above the widgets that use it and handed down, or held in objects a state-management approach provides. ## Classifying a notes app | Piece of state | Who reads or writes it | Must it outlive the screen? | Usually | |---|---|---|---| | Password show/hide | the password field | no | ephemeral | | Selected tab on the home screen | the home screen | no, unless restored | ephemeral | | Draft text of the note being edited | the editor, later the list | yes, if drafts autosave | depends | | Signed-in user | header, list, settings | yes, across restarts | app state | | Notes list | list and detail screens | yes | app state | The draft row is deliberately ambiguous: it starts ephemeral and becomes app state the moment drafts must survive leaving the editor. ## There is no clear-cut rule The docs are explicit that the split is a judgement call. You *can* manage all state with `State` and `setState` — the `flutter create` starter app does — and a piece can migrate in either direction. Questions that settle most cases: 1. **Who reads it?** One widget points to ephemeral; several widgets on different branches or screens point to app state. 2. **Who changes it?** A change triggered far from where the value is shown needs a shared owner. 3. **How long must it live?** Until the widget leaves the tree, until the user signs out, or across restarts. 4. **Would losing it hurt?** Losing a visibility toggle is harmless; losing the signed-in user is not. One related tool keeps a value ephemeral yet durable: **state restoration**. A `State` that mixes in `RestorationMixin` and keeps its field in a `RestorableBool` gets it back after the operating system kills the app in the background, provided the app enables restoration with a `restorationScopeId`. That is still one widget's state, not shared app state. ## Why the distinction matters Classifying state first prevents two opposite mistakes: pushing a toggle into a global store where every screen can see and break it, and hiding the signed-in user inside one screen's `State`, where the next screen cannot reach it.

  • The product team now wants a 'always show passwords' setting remembered across launches. Does the toggle's state change kind?
    Partly. The default visibility becomes app state: a saved preference read at startup and changed from settings, so it lives above the screens. The per-field toggle can stay ephemeral, initialised from that preference. This is the docs' point that a piece of state can move from ephemeral to app state as requirements grow.
  • The OS kills the notes app in the background and the selected home tab resets. Must the tab become app state?
    Not necessarily. Flutter's state restoration keeps UI state across process death while it stays in one widget: mix `RestorationMixin` into the `State`, hold the index in a restorable property such as `RestorableInt`, and give the app a `restorationScopeId`. The value is still ephemeral in scope — nobody else reads it.

saying these in an interview costs you the question

  • Every piece of state in a Flutter app needs a state-management library.
  • A password field's visibility toggle belongs in a global app store.
  • Ephemeral state is state that changes quickly; app state changes slowly.
  • Once state is classified as ephemeral it can never become app state.
  • The signed-in user can live in the sign-in screen's State and be read everywhere.
open as a page

In Flutter, how do you lift state to a common ancestor, and how do child widgets read and change it through constructor values and callbacks?

level: middleimportance: must knowfreq 60%

basics

~20 s

Move the value into the State of the nearest widget above everyone who reads it, pass it down as constructor arguments, and pass callbacks such as ValueChanged<bool> down so children report changes; the owner updates the value with setState.

open as a page

In Flutter, which part of the tree rebuilds when a State calls setState, and why keep ephemeral state in the smallest widget that needs it?

level: juniorimportance: should knowfreq 52%

basics

~20 s

setState reruns build for that one State, and the widgets it returns update down its subtree; ancestors and siblings are untouched. Keeping state in the smallest widget that needs it keeps each rebuild small, which Flutter's docs call pushing state to the leaves.

open as a page

In a growing Flutter notes app, how do you decide where each piece of state lives, and when does passing values and callbacks down stop scaling?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Put each value in the lowest widget above all its readers and writers that lives as long as the value must. Passing values and callbacks down stops scaling when middle widgets only forward them, or when several routes need the same live data.

open as a page