Question #452MediumState ManagementImportant

What is the difference between Ephemeral State and App State in Flutter?

#flutter#state-management#ephemeral-state#app-state#setState#provider

Answer

Overview

Flutter distinguishes between two types of state:

  • Ephemeral State (Local State) — State contained within a single widget, short-lived
  • App State (Shared State) — State shared across multiple widgets/screens, long-lived

Ephemeral State

Ephemeral state is local to a single widget and has a short lifespan — it exists only while the widget is mounted. No other part of the app needs to know about it.

dart
// Ephemeral state — managed with StatefulWidget + setState()
class ExpandableCard extends StatefulWidget {
  const ExpandableCard({super.key});

  
  State<ExpandableCard> createState() => _ExpandableCardState();
}

class _ExpandableCardState extends State<ExpandableCard> {
  bool _isExpanded = false; // Ephemeral state

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        GestureDetector(
          onTap: () => setState(() => _isExpanded = !_isExpanded),
          child: Text(_isExpanded ? 'Collapse' : 'Expand'),
        ),
        if (_isExpanded) const Text('Detailed content here...'),
      ],
    );
  }
}

Examples of Ephemeral State

  • Current page index in a
    text
    PageView
  • Current tab in a
    text
    BottomNavigationBar
  • Animation progress
  • Whether a card is expanded or collapsed
  • Text being typed in a
    text
    TextField
    (before submission)
  • Whether a tooltip is visible

App State

App state is shared across multiple widgets and persists throughout the application lifecycle. Other parts of the app need to read or react to this state.

dart
// App state — managed with Provider
class CartModel extends ChangeNotifier {
  final List<Item> _items = [];

  List<Item> get items => List.unmodifiable(_items);
  int get totalItems => _items.length;
  double get totalPrice => _items.fold(0, (sum, item) => sum + item.price);

  void addItem(Item item) {
    _items.add(item);
    notifyListeners(); // Notify all widgets listening to this state
  }

  void removeItem(Item item) {
    _items.remove(item);
    notifyListeners();
  }
}

// Provide at the top of the widget tree
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CartModel(),
      child: const MyApp(),
    ),
  );
}

// Consume anywhere in the app
class CartIcon extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final itemCount = context.watch<CartModel>().totalItems;
    return Badge(
      label: Text('$itemCount'),
      child: const Icon(Icons.shopping_cart),
    );
  }
}

Examples of App State

  • User authentication status (logged in/out)
  • Shopping cart contents
  • User preferences and settings
  • Notifications / unread message count
  • Data fetched from an API
  • Selected theme (dark/light mode)

Key Differences

FeatureEphemeral StateApp State
ScopeSingle widgetMultiple widgets/screens
LifespanWhile widget is mountedEntire app lifecycle
Who needs itOnly the widget itselfMultiple parts of the app
Management
text
StatefulWidget
+
text
setState()
Provider, Riverpod, Bloc, etc.
ComplexitySimpleCan be complex
Survives navigationNoYes
ExampleTab index, animationAuth status, cart, theme

How to Manage Each

State TypeRecommended Approach
Ephemeral
text
StatefulWidget
+
text
setState()
Simple App State
text
Provider
/
text
ValueNotifier
+
text
InheritedWidget
Complex App State
text
Riverpod
,
text
Bloc
,
text
Redux
Server-synced State
text
Riverpod
with
text
AsyncNotifier
,
text
Bloc
with Repository pattern

The Gray Area

There is no strict rule — sometimes state starts as ephemeral and becomes app state as requirements grow:

dart
// Started as ephemeral (just this screen's search)
// But now other screens need the search query too...
// Time to promote to app state!

// Before: Ephemeral
class _SearchState extends State<SearchScreen> {
  String _query = ''; // Only used here
}

// After: App State (when multiple screens need it)
class SearchModel extends ChangeNotifier {
  String _query = '';
  String get query => _query;
  void updateQuery(String q) { _query = q; notifyListeners(); }
}

Rule of Thumb: If you need to access the state from outside the widget or it must survive navigation, promote it to app state. Otherwise, keep it ephemeral with

text
setState()
.