Life Cycle of Flutter Widgets (Improved)
Author: ChatGPT — GPT-5 Thinking mini
Date: September 19, 2025
Contents
1. Introduction
2. Quick Comparison: Stateless vs Stateful
3. Detailed StatefulWidget Life Cycle (step-by-step)
4. Dart example: StatefulWidget with all lifecycle methods
5. App-level lifecycle (AppLifecycleState & WidgetsBindingObserver)
6. Best practices and common pitfalls
7. Checklist before shipping
8. Flowchart (visual)
9. References & further reading
Introduction
This document explains the life cycle of Flutter widgets with emphasis on StatefulWidget
lifecycle methods, practical usage, common pitfalls, and code examples you can paste into
your Flutter project. It is designed to be developer-friendly and directly usable in reports or
appendices.
Quick Comparison: Stateless vs Stateful
StatelessWidget StatefulWidget
Immutable widget. Build only. Use for Holds mutable state in a separate State
static UI. object.
Simple, lightweight, less overhead. Has lifecycle methods: initState, dispose,
setState, etc.
Detailed StatefulWidget Life Cycle
Below are the common lifecycle callbacks and when to use them. Order of events is given,
followed by notes and tips.
Widget constructor
Called when the widget instance is created. This can be invoked multiple times if the parent
rebuilds and supplies new configuration. Keep constructors cheap—do not perform heavy
work here.
createState()
Called once to create the State object. Do not use BuildContext-dependent APIs here; wait
for initState/didChangeDependencies.
[Link]()
Called once when State is inserted into the tree. Initialize controllers, animation controllers,
streams here. Always call [Link]().
didChangeDependencies()
Called immediately after initState and whenever an inherited widget (e.g., Theme,
MediaQuery, Provider) changes. Obtain values from InheritedWidgets here.
build()
Called frequently to describe the UI. Keep it pure and fast—avoid long-running or side-
effectful code. Use final/local variables and memoize expensive operations.
didUpdateWidget(oldWidget)
Called when the parent widget rebuilds and provides a new configuration (new props). Use
it to compare oldWidget vs widget and update State accordingly.
setState()
Not a callback but the mechanism to tell Flutter to rebuild. Only call setState when mounted
and only update fields used by build().
reassemble()
Debug-only: called during hot reload. Use for temporarily reinitializing debug data only.
deactivate()
Called when State is removed from the tree temporarily. It may be inserted elsewhere later.
dispose()
Called when State will never build again. Clean up controllers, listeners, timers, and cancel
subscriptions. Always call [Link]().
App-level lifecycle: AppLifecycleState & WidgetsBindingObserver
Flutter also exposes application-level lifecycle events (resumed, inactive, paused, detached)
through AppLifecycleState. To observe them, mix in WidgetsBindingObserver in your State
class and override didChangeAppLifecycleState().
Use this for: saving small state before backgrounding, pausing animations, stopping camera,
or disconnecting sockets.
Dart example: StatefulWidget with lifecycle methods
import 'package:flutter/[Link]';
class LifecycleDemo extends StatefulWidget {
const LifecycleDemo({Key? key}) : super(key: key);
@override
_LifecycleDemoState createState() => _LifecycleDemoState();
}
class _LifecycleDemoState extends State<LifecycleDemo> with
WidgetsBindingObserver {
@override
void initState() {
[Link]();
[Link](this);
print('initState: initialize controllers, start listeners');
}
@override
void didChangeDependencies() {
[Link]();
print('didChangeDependencies: context-dependent init');
}
@override
void didUpdateWidget(covariant LifecycleDemo oldWidget) {
[Link](oldWidget);
print('didUpdateWidget: widget configuration changed');
}
@override
Widget build(BuildContext context) {
print('build: build UI');
return Scaffold(
appBar: AppBar(title: Text('Lifecycle Demo')),
body: Center(child: Text('Check your console for lifecycle
logs')),
);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
[Link](state);
print('AppLifecycleState changed to: $state');
}
@override
void deactivate() {
[Link]();
print('deactivate: widget removed from tree temporarily');
}
@override
void dispose() {
[Link](this);
// dispose controllers here
print('dispose: cleanup controllers and listeners');
[Link]();
}
}
Best practices and common pitfalls
• Do heavy initialization (network, DB) outside of build—prefer initState or async functions
called from initState.
• Always dispose controllers (TextEditingController, AnimationController, Streams) in
dispose().
• Avoid calling setState in build or excessively; batch state updates when possible.
• When using async in initState, check if mounted before calling setState after await.
• Use didChangeDependencies to read inherited widgets & Providers.
• Avoid relying on constructor for context-dependent work—context isn't fully available.
• Use WidgetsBindingObserver to handle app-level events (pause/resume).
Checklist before shipping
☑ All controllers disposed?
☑ No long-running work in build()?
☑ Handled app backgrounding/recovery if needed?
☑ Avoid memory leaks: cancelled timers/subscriptions?
☑ Test hot reload behavior if important.
Flowchart (visual)
References & Further Reading
- Flutter official documentation — StatefulWidget & State
- Effective Flutter: performance tips and lifecycle guidance
- API docs for WidgetsBindingObserver and AppLifecycleState