Lifecycle of stateful widgets in Flutter?
Answer
Overview
A StatefulWidget in Flutter goes through multiple lifecycle stages from creation to disposal. Understanding this lifecycle is crucial for managing state, resources, and side effects properly.
StatefulWidget Lifecycle Methods
1. createState()
Called when the framework creates a
StatefulWidgetStatedartclass CounterWidget extends StatefulWidget { _CounterWidgetState createState() => _CounterWidgetState(); }
Called: Once, when the widget is first created
2. initState()
Called once when the
Statedartclass _CounterWidgetState extends State<CounterWidget> { int counter = 0; void initState() { super.initState(); // Called once when State is created print('initState called'); // Initialize data counter = 0; // Start timers, listeners, API calls _loadData(); } Future<void> _loadData() async { // Fetch initial data } }
Use cases:
- Initialize variables
- Start timers or animations
- Add listeners (scroll, text controllers)
- Fetch initial data
Important: Cannot call
setState()initState()3. didChangeDependencies()
Called after
initState()InheritedWidgetdartvoid didChangeDependencies() { super.didChangeDependencies(); print('didChangeDependencies called'); // Safe to call MediaQuery, Theme, etc. final theme = Theme.of(context); final size = MediaQuery.of(context).size; }
Called:
- Right after text
initState() - When dependencies changetext
InheritedWidget
Use cases:
- Access -dependent data (MediaQuery, Theme)text
context - React to inherited widget changes
4. build()
Called to render the widget. Can be called multiple times (after
setState()dartWidget build(BuildContext context) { print('build called'); return Scaffold( appBar: AppBar(title: Text('Counter: $counter')), body: Center( child: Text('Count: $counter'), ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { counter++; }); }, child: Icon(Icons.add), ), ); }
Called:
- After text
didChangeDependencies() - After text
setState() - When parent widget rebuilds
Important: Keep
build()5. didUpdateWidget()
Called when the parent widget rebuilds and provides a new widget with different parameters.
dartclass _CounterWidgetState extends State<CounterWidget> { void didUpdateWidget(CounterWidget oldWidget) { super.didUpdateWidget(oldWidget); print('didUpdateWidget called'); // Compare old and new widget if (widget.initialValue != oldWidget.initialValue) { setState(() { counter = widget.initialValue; }); } } }
Called: When parent rebuilds with new widget configuration
Use cases:
- React to widget parameter changes
- Reset state based on new properties
6. setState()
Triggers a rebuild of the widget. Call this when internal state changes.
dartvoid _incrementCounter() { setState(() { counter++; // Modify state }); // build() is called after setState() }
Important:
- Only call when state actually changes
- Triggers methodtext
build() - Don't call during text
build()
7. deactivate()
Called when the
Statedartvoid deactivate() { print('deactivate called'); super.deactivate(); // Widget might be reinserted later }
Called: When widget is removed from tree (but may be reinserted)
8. dispose()
Called when the
Statedartclass _CounterWidgetState extends State<CounterWidget> { late Timer _timer; late ScrollController _scrollController; void initState() { super.initState(); _timer = Timer.periodic(Duration(seconds: 1), (_) { setState(() => counter++); }); _scrollController = ScrollController(); } void dispose() { print('dispose called'); // Cancel timers _timer.cancel(); // Dispose controllers _scrollController.dispose(); // Remove listeners // Close streams super.dispose(); } }
Use cases:
- Cancel timers
- Dispose controllers (TextEditingController, AnimationController)
- Close streams
- Remove listeners
- Free memory
Important: Always call
super.dispose()Complete Lifecycle Flow
text┌─────────────────────────────────────────────┐ │ StatefulWidget created │ └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 1. createState() │ ← Returns State object └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 2. initState() │ ← Called once (initialization) └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 3. didChangeDependencies() │ ← Access context (Theme, MediaQuery) └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 4. build() │ ← Render widget └─────────────────┬───────────────────────────┘ │ │ (User interaction, setState) │ ▼ ┌────────────────┐ │ setState() │ ─────┐ └────────────────┘ │ │ │ ▼ │ ┌────────────────┐ │ │ build() │ ◄─────┘ (Rebuild loop) └────────────────┘ │ │ (Parent rebuilds with new widget) │ ▼ ┌─────────────────────────────────────────────┐ │ 5. didUpdateWidget(oldWidget) │ ← React to widget changes └─────────────────┬───────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ 6. build() │ └─────────────────┬───────────────────────────┘ │ │ (Widget removed from tree) │ ▼ ┌─────────────────────────────────────────────┐ │ 7. deactivate() │ ← Temporary removal └─────────────────┬───────────────────────────┘ │ │ (Widget permanently removed) │ ▼ ┌─────────────────────────────────────────────┐ │ 8. dispose() │ ← Cleanup └─────────────────────────────────────────────┘
Complete Example
dartclass LifecycleDemo extends StatefulWidget { final String title; const LifecycleDemo({Key? key, required this.title}) : super(key: key); _LifecycleDemoState createState() { print('createState called'); return _LifecycleDemoState(); } } class _LifecycleDemoState extends State<LifecycleDemo> { int counter = 0; late Timer timer; void initState() { super.initState(); print('initState called'); timer = Timer.periodic(Duration(seconds: 1), (_) { setState(() => counter++); }); } void didChangeDependencies() { super.didChangeDependencies(); print('didChangeDependencies called'); } Widget build(BuildContext context) { print('build called - counter: $counter'); return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Counter: $counter', style: TextStyle(fontSize: 32)), SizedBox(height: 20), ElevatedButton( onPressed: () { setState(() => counter++); }, child: Text('Increment'), ), ], ), ), ); } void didUpdateWidget(LifecycleDemo oldWidget) { super.didUpdateWidget(oldWidget); print('didUpdateWidget called - old: ${oldWidget.title}, new: ${widget.title}'); } void deactivate() { print('deactivate called'); super.deactivate(); } void dispose() { print('dispose called'); timer.cancel(); super.dispose(); } }
Lifecycle Method Summary
| Method | Called When | Use For |
|---|---|---|
text | Widget first created | Return State object |
text | State object created (once) | Initialize variables, start timers |
text | Dependencies change | Access context (Theme, MediaQuery) |
text | Render widget (multiple times) | Build UI |
text | Parent rebuilds with new widget | React to widget changes |
text | State changes | Trigger rebuild |
text | Widget removed (temporary) | Pause operations |
text | Widget removed (permanent) | Cleanup resources |
Best Practices
| Practice | Reason |
|---|---|
| Initialize in text | Called once, good for setup |
| Clean up in text | Prevent memory leaks |
| Keep text | No side effects, no async |
| Use text | Safe to access Theme, MediaQuery |
| Don't call text text | Causes infinite loop |
| Always call text | Ensures parent cleanup |
Common Mistakes
dart// ❌ Wrong - setState() in build() Widget build(BuildContext context) { setState(() => counter++); // Infinite loop! return Text('$counter'); } // ❌ Wrong - Async in initState() without proper handling void initState() { super.initState(); fetchData(); // OK, but cannot use setState directly } // ✅ Correct - Async in initState() void initState() { super.initState(); _loadData(); } Future<void> _loadData() async { final data = await fetchData(); if (mounted) { setState(() { // Update state }); } } // ❌ Wrong - Not disposing controllers void dispose() { // Forgot to dispose _controller super.dispose(); } // ✅ Correct - Dispose all resources void dispose() { _controller.dispose(); _timer.cancel(); super.dispose(); }
Summary
StatefulWidget lifecycle:
→textcreateState()→textinitState()→textdidChangeDependencies()→textbuild()→textsetState()(loop) →textbuild()→textdidUpdateWidget()→textdeactivate(). Usetextdispose()for setup andtextinitState()for cleanup.textdispose()
Learn more at Flutter Lifecycle Documentation.