D
Flutter CoreEasy20 XP5 min read

Explain the complete lifecycle of a StatefulWidget

TL;DR: The lifecycle goes: createState → initState → didChangeDependencies → build → (didUpdateWidget / setState → build)* → deactivate → dispose.

Full Answer

createState()

Called once when the StatefulWidget is first inserted into the tree. Creates the mutable State object.

initState()

Called once after the State is created. Use for one-time initialization: subscribe to streams, initialize AnimationControllers, register listeners. Never call BuildContext operations that require the tree to be complete here — use didChangeDependencies instead.

didChangeDependencies()

Called immediately after initState and whenever an InheritedWidget dependency changes. Safe to call context.dependOn… here. Useful for fetching data that depends on inherited data like Theme or MediaQuery.

build()

Called after initState, didChangeDependencies, setState, and didUpdateWidget. Must be pure and fast — no side effects, no heavy computation. Returns the widget subtree.

didUpdateWidget(oldWidget)

Called when the parent rebuilds and provides a new widget configuration with the same runtimeType. Use to react to property changes, e.g., cancel and restart an animation if a duration property changed.

deactivate()

Called when the State is temporarily removed from the tree (e.g., during a navigation transition). Rarely overridden.

dispose()

Called when the State is permanently removed. Always dispose: AnimationControllers, TextEditingControllers, FocusNodes, StreamSubscriptions, Timers. Failure to dispose causes memory leaks.

🚫

Never call setState() in dispose(). Always call super.dispose() at the end. Check mounted before calling setState() in async callbacks to avoid calling it after dispose.

Code Examples

dartComplete lifecycle demonstration
Output
Console: initState — one-time setup
Console: didChangeDependencies — safe to use context fully
Console: (on parent rebuild with new title) title changed: Old → New

Common Mistakes

  • Calling super.dispose() before cleaning up resources — always clean up first, then call super.
  • Using BuildContext in initState synchronously for inherited widgets — use didChangeDependencies.
  • Not cancelling stream subscriptions — leads to memory leaks and setState on dead widgets.

Interview Tip

💡

The mounted check pattern: `if (mounted) setState(() { ... })` is essential for any async operation. In Dart 3+, prefer using ref.listen (Riverpod) or BLoC's stream to avoid the need for mounted checks entirely.

#lifecycle#stateful#initState#dispose#setState