Question #6MediumFlutter BasicsImportant

Lifecycle of stateful widget and explain everything

#widget#state

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

text
createState() → 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.

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

dart

void 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).

dart

void 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.

dart

Widget 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.

dart

void 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.

dart
void _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).

dart

void 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.

dart

void 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

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

Resources