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 (before submission)text
TextField - 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
| Feature | Ephemeral State | App State |
|---|---|---|
| Scope | Single widget | Multiple widgets/screens |
| Lifespan | While widget is mounted | Entire app lifecycle |
| Who needs it | Only the widget itself | Multiple parts of the app |
| Management | text text | Provider, Riverpod, Bloc, etc. |
| Complexity | Simple | Can be complex |
| Survives navigation | No | Yes |
| Example | Tab index, animation | Auth status, cart, theme |
How to Manage Each
| State Type | Recommended Approach |
|---|---|
| Ephemeral | text text |
| Simple App State | text text text |
| Complex App State | text text text |
| Server-synced State | text text text |
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
.textsetState()