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 позначає себе як брудний та планує перебудову на наступний кадр. Важливо: setState не викликає build негайно — він лише реєструє необхідність перебудови. Flutter збирає всі брудні елементи за поточний кадр та перебудовує їх пакетно, що оптимізує продуктивність. Після виклику build State повертається до стану чистий.
Властивість 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 (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 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 — змінюваний об'єкт, що зберігає дані та керує життєвим циклом. Віджет може бути перестворений, 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також