Question #363MediumFlutter Basics

Lifecycle of stateful widgets in Flutter?

#stateful-widget#lifecycle#state-management

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

text
StatefulWidget
. This is the first method called and returns a
text
State
object.

dart
class CounterWidget extends StatefulWidget {
  
  _CounterWidgetState createState() => _CounterWidgetState();
}

Called: Once, when the widget is first created


2. initState()

Called once when the

text
State
object is first created. Use this for initialization logic.

dart
class _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

text
setState()
in
text
initState()
directly (widget not built yet)


3. didChangeDependencies()

Called after

text
initState()
and whenever the widget's dependencies change (e.g.,
text
InheritedWidget
changes).

dart

void 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
    text
    InheritedWidget
    dependencies change

Use cases:

  • Access
    text
    context
    -dependent data (MediaQuery, Theme)
  • React to inherited widget changes

4. build()

Called to render the widget. Can be called multiple times (after

text
setState()
, parent rebuild, etc.).

dart

Widget 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

text
build()
pure (no side effects, no async operations)


5. didUpdateWidget()

Called when the parent widget rebuilds and provides a new widget with different parameters.

dart
class _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.

dart
void _incrementCounter() {
  setState(() {
    counter++; // Modify state
  });
  // build() is called after setState()
}

Important:

  • Only call when state actually changes
  • Triggers
    text
    build()
    method
  • Don't call during
    text
    build()

7. deactivate()

Called when the

text
State
object is removed from the widget tree temporarily (e.g., when navigating away).

dart

void 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

text
State
object is permanently removed. Use this to clean up resources.

dart
class _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

text
super.dispose()
at the end


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

dart
class 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

MethodCalled WhenUse For
text
createState()
Widget first createdReturn State object
text
initState()
State object created (once)Initialize variables, start timers
text
didChangeDependencies()
Dependencies changeAccess context (Theme, MediaQuery)
text
build()
Render widget (multiple times)Build UI
text
didUpdateWidget()
Parent rebuilds with new widgetReact to widget changes
text
setState()
State changesTrigger rebuild
text
deactivate()
Widget removed (temporary)Pause operations
text
dispose()
Widget removed (permanent)Cleanup resources

Best Practices

PracticeReason
Initialize in
text
initState()
Called once, good for setup
Clean up in
text
dispose()
Prevent memory leaks
Keep
text
build()
pure
No side effects, no async
Use
text
didChangeDependencies()
for context
Safe to access Theme, MediaQuery
Don't call
text
setState()
in
text
build()
Causes infinite loop
Always call
text
super.dispose()
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:

text
createState()
text
initState()
text
didChangeDependencies()
text
build()
text
setState()
text
build()
(loop) →
text
didUpdateWidget()
text
deactivate()
text
dispose()
. Use
text
initState()
for setup and
text
dispose()
for cleanup.

Learn more at Flutter Lifecycle Documentation.