У StatelessWidget жизненного цикла почти нет: создали и вызвали build. У State он полноценный, и половина багов в Flutter-проектах это код не в том методе.
Методы State по порядку
createState(у виджета): фреймворк создаёт объектStateпри вставке элемента в дерево.initState: один раз. Здесь создают контроллеры (TextEditingController,AnimationController), подписки, запускают начальную загрузку. Обязательноsuper.initState()первой строкой. Черезcontextздесь нельзя подписываться наInheritedWidget:Theme.of(context)вinitStateбросит ошибку.didChangeDependencies: сразу послеinitStateи потом каждый раз, когда изменилсяInheritedWidget, от которого зависитState(тема, локаль,MediaQuery, провайдер). Тут безопасно читать зависимости черезcontext.build: может вызываться много раз в секунду. Должен быть чистым и быстрым: никаких запросов и подписок.didUpdateWidget(oldWidget): родитель перестроился и передал новый виджет того же типа и ключа.Stateсохранился, а параметры могли поменяться. Если зависели отwidget.userId, сравнивают со старым и перезапрашивают. После него всегда вызываетсяbuild.setState: помечает элемент грязным,buildбудет на следующем кадре.deactivate: элемент убран из дерева. Может вернуться (перенос черезGlobalKey) до конца кадра, тогда будетactivate.dispose: окончательно. Освободить всё, что создали:controller.dispose(),subscription.cancel(),removeListener.super.dispose()последней строкой.
class _ProfileState extends State<Profile> {
late final TextEditingController _name;
StreamSubscription<User>? _sub;
@override
void initState() {
super.initState();
_name = TextEditingController();
_sub = widget.repo.watch(widget.id).listen(_onUser);
}
@override
void didUpdateWidget(Profile old) {
super.didUpdateWidget(old);
if (old.id != widget.id) {
_sub?.cancel();
_sub = widget.repo.watch(widget.id).listen(_onUser);
}
}
void _onUser(User u) {
if (!mounted) return;
setState(() => _name.text = u.name);
}
@override
void dispose() {
_sub?.cancel();
_name.dispose();
super.dispose();
}
// build ...
}
mounted и setState после dispose
Асинхронный ответ может прийти, когда пользователь уже ушёл с экрана. setState после dispose бросает ошибку «setState() called after dispose()». Проверка if (!mounted) return; после каждого await в State и отмена подписок в dispose решают проблему.
Жизненный цикл приложения
AppLifecycleState сообщает, что делает всё приложение:
resumed: на экране и получает ввод;inactive: видно, но без ввода (входящий звонок, переключатель приложений на iOS);hidden: не видно (появилось в Flutter 3.13);paused: в фоне;detached: движок работает без окна.
Подписка: миксин WidgetsBindingObserver с методом didChangeAppLifecycleState или класс AppLifecycleListener с колбэками onResume, onPause и другими. Типичные задачи: поставить видео на паузу, сохранить черновик, переподключить сокет при возврате.
Частые ошибки
Theme.of(context)вinitState.- Запрос данных в
build: он уйдёт на каждом перестроении. - Забытый
disposeу контроллера или подписки. - Игнорировать
didUpdateWidget: экран показывает данные старогоid.
Что спросят на собесе
- Порядок методов
Stateот создания до удаления. - Зачем
didChangeDependenciesи почему нельзя читатьInheritedWidgetвinitState. - Когда вызывается
didUpdateWidgetи что в нём делать. - Что такое
mounted, откуда ошибка «setState after dispose». - Состояния
AppLifecycleStateи как на них подписаться.