State — центральный объект управления данными во Flutter, ассоциированный с StatefulWidget и отвечающий за хранение изменяемой информации и построение интерфейса. По данным официальной документации Flutter (Flutter.dev, 2026), State существует на протяжении всего жизненного цикла виджета и переживает его перестроения, обеспечивая консистентность данных между обновлениями UI. В отличие от самого виджета, State может изменять свои поля и инициировать перестроение через вызов setState.
Главное
State — это объект в архитектуре Flutter, который хранит изменяемые данные StatefulWidget и определяет, как эти данные отображаются в интерфейсе. Каждый StatefulWidget при встраивании в дерево создаёт ровно один объект State через метод createState. State существует независимо от виджета: если родитель перестраивает StatefulWidget с новыми параметрами, State остаётся прежним и получает обновлённый виджет через свойство widget.
По данным Flutter Architectural Overview (Google, 2026), разделение Widget и State — сознательное архитектурное решение, позволяющее фреймворку переиспользовать элементы дерева. Виджет (лёгкое описание) может быть создан и уничтожен многократно, но State (тяжёлый объект с данными) остаётся в памяти, пока элемент находится в дереве. Это предотвращает потерю данных при частых перестроениях родительских виджетов.
State реализует интерфейс StatefulWidget через дженерик: class _MyState extends State<MyWidget>. Дженерик связывает State с конкретным типом StatefulWidget, обеспечивая типобезопасный доступ к его полям через свойство widget.
Объект State хранится в StatefulElement — промежуточном слое между Widget и RenderObject. StatefulElement создаёт State через createState, сохраняет ссылку на него и передаёт State в качестве владельца. Element уничтожается, только когда виджет удаляется из дерева — до этого момента State живёт в памяти.
Жизненный цикл State детерминирован и состоит из строгой последовательности вызовов. Понимание этой последовательности — основа корректной работы с ресурсами и предотвращения утечек памяти.
initState вызывается первым при создании State. В этом методе инициализируются контроллеры, подписки на потоки данных, таймеры и начальные значения полей. Обязателен вызов super.initState() в первой строке. На этапе initState дерево виджетов ещё не полностью смонтировано, поэтому методы вроде MediaQuery.of(context) могут работать некорректно.
didChangeDependencies вызывается после initState и при каждом изменении InheritedWidget-зависимостей. Именно здесь, а не в initState, следует вызывать MediaQuery.of(context) или Theme.of(context), так как к этому моменту дерево уже смонтировано. Этот метод также вызывается, если виджет перемещается в другой контекст, где InheritedWidget предоставляет другие значения.
build — основной метод State, возвращающий дерево виджетов. Вызывается после initState, после didChangeDependencies и после каждого setState. Метод build не должен иметь побочных эффектов — он только описывает интерфейс на основе текущих значений полей State.
didUpdateWidget вызывается, когда родитель перестраивает StatefulWidget с новыми параметрами. State получает доступ к старому виджету через oldWidget и может сравнить его с новым. Если параметры изменились, можно обновить состояние, загрузить новые данные или перезапустить анимацию.
dispose — завершающий метод, в котором освобождаются все ресурсы: контроллеры, подписки, таймеры. После dispose State помечается как мёртвый: mounted возвращает false, вызов setState выбрасывает исключение. Обязателен вызов super.dispose() в последней строке метода.
| Метод | Когда вызывается | Обязательный super |
|---|---|---|
| initState | При создании State | Да, в первой строке |
| didChangeDependencies | После initState и при смене InheritedWidget | Да |
| build | После initState, didChangeDependencies, setState | Нет |
| didUpdateWidget | При новом виджете от родителя | Да |
| setState | По вызову разработчика | Нет |
| dispose | При удалении из дерева | Да, в последней строке |
Механизм работы State основан на трёх ключевых принципах: ассоциация с Element, реактивность через setState и доступ к родителю через свойство widget. Когда Flutter строит дерево элементов и встречает StatefulElement, он вызывает createState связанного виджета. Созданный State сохраняется в элементе и существует до тех пор, пока элемент не будет удалён.
При вызове setState State помечает себя как «грязный» (dirty) и планирует перестроение на следующий кадр. Важно: setState не вызывает build немедленно — он лишь регистрирует необходимость перестроения. Flutter собирает все dirty-элементы за текущий кадр и перестраивает их пакетно, что оптимизирует производительность. После вызова build State возвращается в состояние «чистый» (clean).
Свойство widget позволяет State читать параметры, переданные в конструктор StatefulWidget. Поскольку StatefulWidget иммутабелен (как StatelessWidget), его поля не меняются — при изменении параметров родитель создаёт новый виджет, а State получает его через didUpdateWidget. Это гарантирует, что State всегда работает с актуальными данными родителя.
Базовый пример State с полем, изменяемым по таймеру. Демонстрирует initState, setState и dispose:
class _TimerWidgetState extends State<TimerWidget> {
int _seconds = 0;
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(
const Duration(seconds: 1),
(_) => setState(() => _seconds++),
);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('$_seconds seconds elapsed');
}
}
Пример с использованием widget свойства для доступа к параметрам родителя и реагирования на их изменения через didUpdateWidget:
class _GreetingState extends State<GreetingWidget> {
String _displayName = '';
@override
void initState() {
super.initState();
_displayName = _formatName(widget.name);
}
@override
void didUpdateWidget(GreetingWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.name != oldWidget.name) {
setState(() {
_displayName = _formatName(widget.name);
});
}
}
String _formatName(String name) => name.trim().isEmpty ? 'Guest' : name;
@override
Widget build(BuildContext context) {
return Text('Hello, $_displayName!');
}
}
Во втором примере State отслеживает изменение входного параметра name и переформатирует отображение только при реальном изменении. Без проверки widget.name != oldWidget.name метод вызывался бы при каждом перестроении родителя, даже если имя не изменилось — это лишняя работа для фреймворка.
State и StatefulWidget — два разных класса в архитектуре Flutter, выполняющие разные роли. StatefulWidget — это лёгкая иммутабельная обёртка, которая описывает конфигурацию виджета и создаёт State. State — это тяжёлый объект, который хранит изменяемые данные, управляет подписками и строит UI. Такое разделение позволяет Flutter уничтожать и создавать виджеты без потери состояния.
Все поля StatefulWidget должны быть final и задаваться в конструкторе — они не меняются после создания. State, напротив, может изменять свои поля в любой момент, но все изменения должны предваряться вызовом setState, чтобы Flutter узнал о необходимости перестроения. Это ключевое отличие: StatefulWidget — это «что показать», State — «как показать и какие данные использовать».
По данным Flutter source code analysis (Flutter SDK, 2026), StatefulWidget содержит всего одно обязательное поле — createState, в то время как State имеет доступ к BuildContext, может подписываться на потоки, управлять анимациями и контроллерами. Рекомендуется держать StatefulWidget максимально простым, перенося всю логику в State.
Разделение Widget и State — архитектурное решение, обеспечивающее иммутабельность конфигурации. Если бы StatefulWidget сам хранил состояние, при каждом перестроении родителя состояние бы терялось. Вынося состояние в отдельный объект, Flutter гарантирует, что данные переживают перестроения, а виджеты остаются лёгкими и сравнимыми.
Объект State изолирован — он не имеет прямого доступа к State других виджетов. Для обмена данными между виджетами используются InheritedWidget или внешние инструменты управления состоянием: Provider, Riverpod, Bloc, Redux. Каждый подход решает задачу по-своему: InheritedWidget работает через дерево виджетов, Provider — через DI-контейнер, Bloc — через потоки событий.
Выбор инструмента зависит от масштаба проекта. Для маленького приложения достаточно InheritedWidget и локального State. Для среднего и крупного проекта рекомендуется Riverpod или Bloc — они обеспечивают тестируемость, предсказуемость и отделение логики от UI. State при этом используется только для локальных данных виджета (фокус, скролл, анимация).
По данным Flutter Community Survey 2025 (Flutter Foundation, декабрь 2025), Riverpod является самым популярным решением для управления состоянием в новых проектах (38%), за ним следуют Bloc (31%) и Provider (22%). Все три инструмента совместимы со State и не требуют отказа от стандартного жизненного цикла.
Первая ошибка — забыть проверить mounted перед setState в асинхронном колбэке. Когда виджет удалён из дерева (например, пользователь ушёл с экрана), но асинхронная операция (HTTP-запрос) ещё выполняется, после её завершения State уже мёртв. Вызов setState в мёртвом State выбрасывает исключение. Проверка if (mounted) setState(...) решает проблему.
Вторая ошибка — инициализация InheritedWidget-зависимостей в initState вместо didChangeDependencies. В initState контекст ещё не смонтирован, поэтому MediaQuery.of(context) выбросит исключение. Все зависимости от InheritedWidget должны настраиваться в didChangeDependencies или в build.
Третья ошибка — мутация полей без вызова setState. Если разработчик изменяет поле State без setState, Flutter не узнает об изменении и UI не обновится. Например: _list.add(item) без последующего setState((){}) изменит список, но экран останется прежним.
Паттерн безопасности для асинхронных операций в State:
Future<void> _fetchData() async {
final data = await ApiService.fetch();
if (mounted) {
setState(() => _data = data);
}
}
Проверка mounted гарантирует, что setState вызывается только для живого State, предотвращая исключение «setState called after dispose».
Часто задаваемые вопросы
StatefulWidget — иммутабельная конфигурация виджета, а State — изменяемый объект, хранящий данные и управляющий жизненным циклом. Widget может быть пересоздан, State — нет. StatefulWidget создаёт State через createState.
Ровно один. Метод createState вызывается однократно при первом встраивании StatefulWidget в дерево. Даже если родитель перестраивается многократно, объект State остаётся тем же самым, пока не изменится тип или Key виджета.
mounted — булев флаг, показывающий, находится ли State в дереве виджетов. После вызова dispose mounted становится false. Используется для проверки перед setState в асинхронных колбэках, чтобы избежать исключения.
Нет. State всегда привязан к конкретному StatefulWidget через дженерик: State<T extends StatefulWidget>. Создать State напрямую, без ассоциации с виджетом, архитектурно невозможно.
Выбросится исключение «setState called after dispose». После вызова dispose State считается мёртвым, и любые попытки перестроить UI через setState запрещены. Решение — проверять mounted перед каждым setState.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также