Ответ
Нет, initState() вызывается не всегда. Это метод жизненного цикла, который вызывается ровно один раз при первом встраивании (insert) объекта State в дерево виджетов.
Когда initState() НЕ вызывается:
- Если объект
Stateбыл создан, но соответствующийStatefulWidgetникогда не был добавлен в дерево (например, из-за условного оператораif (false) MyWidget()). - При горячей перезагрузке (Hot Reload) состояние сохраняется, и
initState()не вызывается заново. - Если виджет был удален из дерева и затем снова добавлен с тем же ключом (
key), Flutter может переиспользовать существующий объектState, иinitState()вызван не будет.
Типичный сценарий использования в Flutter:
class _MyHomePageState extends State<MyHomePage> {
late StreamSubscription<int> _streamSubscription;
final _scrollController = ScrollController();
ComplexData? _cachedData;
@override
void initState() {
super.initState(); // Всегда вызывайте super.initState() первым!
print('initState: Виджет встроен в дерево.');
// Идеальное место для:
// 1. Инициализации final/ late final полей, зависящих от widget.
_cachedData = ComplexData(widget.initialParam);
// 2. Подписки на Stream или Listenable.
_streamSubscription = myStream.listen(_handleData);
_scrollController.addListener(_onScroll);
// 3. Запуска одноразовых асинхронных операций (но не async/await).
WidgetsBinding.instance.addPostFrameCallback((_) {
// Код, который должен выполниться после первого построения кадра.
});
_loadInitialData(); // Метод, который запускает Future.
}
Future<void> _loadInitialData() async {
final data = await Api.fetchData();
setState(() => _cachedData = data);
}
@override
void dispose() {
// Все, что создано в initState, должно быть очищено в dispose.
_streamSubscription.cancel();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
controller: _scrollController,
children: [
Text('Данные: ${_cachedData?.value}'),
],
),
);
}
}
Гарантия: Если виджет был вставлен в дерево и у него вызвался build(), то initState() был вызван ранее.
Ответ 18+ 🔞
Э, слушай, вот объясняю про этот ваш initState(). Многие думают, что он вызывается всегда, как часы. А нихуя! Он вызывается ровно один раз, когда объект State впервые впихивают в дерево виджетов. И точка.
А вот когда initState() НЕ вызывается, ёпта:
- Если ты создал
State, а виджет так и не сунул в дерево. Ну, типа,if (false) MyWidget()— и всё, манда с ушами, объект есть, аinitState()— нет. - При горячей перезагрузке (Hot Reload). Состояние же сохраняется, зачем его заново инитить? Не будет тебе повторного вызова, хоть тресни.
- Если виджет выкинули, а потом вернули с тем же самым ключом (
key). Flutter — хитрая жопа, он может поднять старыйStateиз помойки и переиспользовать.initState()уже отработал, второй раз не позовут.
Короче, типичный сценарий, как этим пользоваться:
class _MyHomePageState extends State<MyHomePage> {
late StreamSubscription<int> _streamSubscription;
final _scrollController = ScrollController();
ComplexData? _cachedData;
@override
void initState() {
super.initState(); // Это, бля, святое! Всегда первым делом!
print('initState: Виджет встроен в дерево, ура!');
// Сюда пихаем всё, что должно случиться один раз при рождении:
// 1. Инициализация полей, которые от `widget` зависят.
_cachedData = ComplexData(widget.initialParam);
// 2. Подписки на всякие Stream и Listenable. Чтобы потом не орать "куда делись данные?!".
_streamSubscription = myStream.listen(_handleData);
_scrollController.addListener(_onScroll);
// 3. Запуск асинхронных делишек (но сам метод `initState` не делай async!).
WidgetsBinding.instance.addPostFrameCallback((_) {
// Код, который выполнится после того, как первый кадр отрисуется.
});
_loadInitialData(); // А вот это уже async-метод, который дергаем отсюда.
}
Future<void> _loadInitialData() async {
final data = await Api.fetchData();
setState(() => _cachedData = data); // Вот тут обновляем состояние.
}
@override
void dispose() {
// Всё, что создал в `initState`, тут же и прибивай! Иначе утечки памяти будут — овердохуища.
_streamSubscription.cancel();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
controller: _scrollController,
children: [
Text('Данные: ${_cachedData?.value}'),
],
),
);
}
}
Железобетонная гарантия: Если виджет встроился в дерево и у него вызвался build(), будь уверен — initState() перед этим уже отработал. Иначе никак, чувак.