What are the lifecycle methods of a `State`, and what is each one for?
Show answer — try answering out loud first
The main ones are: initState (called once when the state is created), didChangeDependencies (when inherited dependencies change), build (every time something needs to be drawn), didUpdateWidget (when the widget is reconfigured), setState (to request a rebuild), and dispose (called once on destruction, to release resources).
The typical lifecycle order of a State:
initState(): runs only once, when it's inserted into the tree. Here you initialize controllers, subscriptions, or initial requests. You can't yet safely usecontextforInheritedWidget.didChangeDependencies(): right afterinitStateand every time anInheritedWidgetyou depend on changes. A good place to read aProviderthat can change.build(): builds the UI. It's called many times; it should be fast and free of side effects.didUpdateWidget(old): when the parent widget rebuilds and passes a new configuration. Here you compare witholdWidgetto react to parameter changes.setState(): marks the state as "dirty" and schedules abuild.dispose(): runs once, when the widget is removed from the tree for good. Here you release resources: cancel streams,dispose()controllers, etc.
import 'package:flutter/material.dart';
class Clock extends StatefulWidget {
const Clock({super.key});
@override
State<Clock> createState() => _ClockState();
}
class _ClockState extends State<Clock> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(); // initialize resources
print("initState: only once");
}
@override
void didUpdateWidget(covariant Clock oldWidget) {
super.didUpdateWidget(oldWidget);
print("The widget was reconfigured");
}
@override
void dispose() {
_controller.dispose(); // ALWAYS release resources
print("dispose: final cleanup");
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(controller: _controller);
}
}
Not releasing resources in dispose(). If you create a TextEditingController, an AnimationController, a Timer, or a subscription to a Stream and don't release them, you create memory leaks and warnings in the console. Rule: everything you initialize in initState (or that has a dispose) must be cleaned up in dispose.
"The cycle starts with
initState, which runs only once and where I initialize controllers and subscriptions. ThendidChangeDependencieswhen inherited dependencies change.buildis called many times to draw and must be pure and fast.didUpdateWidgetruns when the parent passes me a new configuration.setStateschedules a rebuild. Anddisposeruns once at the end, where I release everything I opened to avoid memory leaks. The key rule is that everything I open ininitStateI close indispose."
How many times does initState run during the life of a State?
See answer
Exactly once, when the State is first inserted into the tree. Even if the widget rebuilds many times (calling build repeatedly), initState doesn't run again. Its counterpart, dispose, also runs only once, when the widget is removed from the tree.