Lifecycle of stateful widget and explain everything
Answer
Overview
The StatefulWidget lifecycle consists of multiple methods called at different stages of a widget's existence. Understanding this lifecycle is crucial for managing state, initializing resources, and cleaning up properly.
Lifecycle Diagram
textcreateState() → mounted = true → initState() → didChangeDependencies() → build() ↓ setState() → build() → didUpdateWidget() → build() ↓ deactivate() → dispose() → mounted = false
Lifecycle Methods
1. createState()
Called when the framework creates a StatefulWidget. Returns the State object.
dartclass MyWidget extends StatefulWidget { _MyWidgetState createState() => _MyWidgetState(); }
When called: Once, when widget is first created Purpose: Create and return the State object
2. initState()
First method called after the State object is created.
dartvoid initState() { super.initState(); // Initialize variables _controller = AnimationController(vsync: this); // Fetch data _loadData(); // Subscribe to streams _subscription = stream.listen((data) {}); }
When called: Once, after createState() Cannot: Call setState(), access inherited widgets Can: Initialize controllers, start animations, fetch data Must: Call super.initState() first
3. didChangeDependencies()
Called when dependencies change (InheritedWidget updates).
dartvoid didChangeDependencies() { super.didChangeDependencies(); // Access inherited widgets safely final theme = Theme.of(context); final locale = Localizations.localeOf(context); }
When called:
- After initState()
- Whenever InheritedWidget dependencies change Can: Access BuildContext, call setState()
4. build()
Builds the widget tree. Called frequently.
dartWidget build(BuildContext context) { return Scaffold( body: Text('Count: $_counter'), ); }
When called:
- After didChangeDependencies()
- After setState()
- After didUpdateWidget()
- When parent rebuilds Must: Return a Widget, be pure (no side effects)
5. didUpdateWidget()
Called when widget configuration changes.
dartvoid didUpdateWidget(MyWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.title != oldWidget.title) { // Update state based on new widget properties _updateTitle(); } }
When called: When parent rebuilds with new widget instance Use: Compare old and new widget properties
6. setState()
Notifies framework that internal state changed.
dartvoid _incrementCounter() { setState(() { _counter++; // Update state inside callback }); }
When called: By you, to trigger rebuild Effect: Schedules build() call Cannot: Call during build()
7. deactivate()
Called when widget is removed from tree (temporarily).
dartvoid deactivate() { super.deactivate(); // Widget removed from tree }
When called: Before dispose(), or when moved in tree Rare use: Usually not needed
8. dispose()
Called when widget is permanently removed. Clean up here.
dartvoid dispose() { // Clean up resources _controller.dispose(); _subscription.cancel(); _textController.dispose(); super.dispose(); // Call last! }
When called: When widget is permanently removed Must: Clean up controllers, streams, listeners Must: Call super.dispose() at the end
Complete Example
dartclass CounterWidget extends StatefulWidget { final String title; CounterWidget({required this.title}); _CounterWidgetState createState() { print('1. createState()'); return _CounterWidgetState(); } } class _CounterWidgetState extends State<CounterWidget> { int _counter = 0; late String _title; void initState() { super.initState(); print('2. initState()'); _title = widget.title; } void didChangeDependencies() { super.didChangeDependencies(); print('3. didChangeDependencies()'); } Widget build(BuildContext context) { print('4. build()'); return Column( children: [ Text('$_title: $_counter'), ElevatedButton( onPressed: () => setState(() => _counter++), child: Text('Increment'), ), ], ); } void didUpdateWidget(CounterWidget oldWidget) { super.didUpdateWidget(oldWidget); print('5. didUpdateWidget()'); if (widget.title != oldWidget.title) { _title = widget.title; } } void deactivate() { print('6. deactivate()'); super.deactivate(); } void dispose() { print('7. dispose()'); super.dispose(); } }
Key Points
- initState(): Initialize once, no BuildContext access
- didChangeDependencies(): Access InheritedWidgets
- build(): Pure function, called frequently
- setState(): Triggers rebuild
- dispose(): Clean up resources, call super.dispose() last
- mounted: Check before setState() to avoid errors
Best Practices
Important: Always call super.method() in lifecycle methods
dart// ✅ Good void initState() { super.initState(); // First _initialize(); } // ❌ Bad void initState() { _initialize(); super.initState(); // Wrong position } // ✅ Good (dispose) void dispose() { _cleanup(); super.dispose(); // Last }