State: що це таке, управління станом та принцип роботи

Автор: IT Sectr Опубліковано: 2026-07-01 Час читання: 9 хв

State — центральний об'єкт управління даними у Flutter, асоційований з StatefulWidget та відповідальний за зберігання змінюваної інформації та побудову інтерфейсу. За даними офіційної документації Flutter (Flutter.dev, 2026), State існує протягом всього життєвого циклу віджета та переживає його перебудови, забезпечуючи консистентність даних між оновленнями UI. На відміну від самого віджета, State може змінювати свої поля та ініціювати перебудову через виклик setState.

Головне

  • State — об'єкт, що зберігає змінювані дані StatefulWidget та керує його перебудовою через setState
  • Життєвий цикл — State проходить через initState, didChangeDependencies, build, didUpdateWidget та dispose, кожен етап з чітким призначенням
  • mounted — прапорець, що вказує, чи State все ще знаходиться в дереві віджетів і може безпечно викликати setState
  • widget — посилання на пов'язаний StatefulWidget, доступне через властивість State для читання параметрів батька
  • Ізоляція — State ізольований від інших State; для обміну даними використовуються InheritedWidget або зовнішні інструменти управління станом

Що таке State у Flutter?

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?

Об'єкт State зберігається в StatefulElement — проміжному шарі між Widget та RenderObject. StatefulElement створює State через createState, зберігає посилання на нього та передає State як власника. Element знищується тільки коли віджет видаляється з дерева — до цього моменту State живе в пам'яті.

Життєвий цикл State

Життєвий цикл State детермінований і складається з суворої послідовності викликів. Розуміння цієї послідовності — основа коректної роботи з ресурсами та запобігання витокам пам'яті.

initState — ініціалізація

initState викликається першим при створенні State. У цьому методі ініціалізуються контролери, підписки на потоки даних, таймери та початкові значення полів. Обов'язковий виклик super.initState() у першому рядку. На етапі initState дерево віджетів ще не повністю змонтовано, тому методи на кшталт MediaQuery.of(context) можуть працювати некоректно.

didChangeDependencies

didChangeDependencies викликається після initState та при кожній зміні InheritedWidget-залежностей. Саме тут, а не в initState, слід викликати MediaQuery.of(context) або Theme.of(context), оскільки до цього моменту дерево вже змонтовано. Цей метод також викликається, якщо віджет переміщується в інший контекст, де InheritedWidget надає інші значення.

build — побудова UI

build — основний метод State, що повертає дерево віджетів. Викликається після initState, після didChangeDependencies та після кожного setState. Метод build не повинен мати побічних ефектів — він лише описує інтерфейс на основі поточних значень полів State.

didUpdateWidget

didUpdateWidget викликається, коли батько перебудовує StatefulWidget з новими параметрами. State отримує доступ до старого віджета через oldWidget та може порівняти його з новим. Якщо параметри змінилися, можна оновити стан, завантажити нові дані або перезапустити анімацію.

dispose — звільнення ресурсів

dispose — завершальний метод, в якому звільняються всі ресурси: контролери, підписки, таймери. Після dispose State позначається як мертвий: mounted повертає false, виклик setState викидає виняток. Обов'язковий виклик super.dispose() в останньому рядку методу.

МетодКоли викликаєтьсяОбов'язковий super
initStateПри створенні StateТак, у першому рядку
didChangeDependenciesПісля initState та при зміні InheritedWidgetТак
buildПісля initState, didChangeDependencies, setStateНі
didUpdateWidgetПри новому віджеті від батькаТак
setStateЗа викликом розробникаНі
disposeПри видаленні з дереваТак, в останньому рядку

Як працює State?

Механізм роботи State заснований на трьох ключових принципах: асоціація з Element, реактивність через setState та доступ до батька через властивість widget. Коли Flutter будує дерево елементів та зустрічає StatefulElement, він викликає createState пов'язаного віджета. Створений State зберігається в елементі та існує доти, доки елемент не буде видалено.

При виклику setState State позначає себе як брудний та планує перебудову на наступний кадр. Важливо: setState не викликає build негайно — він лише реєструє необхідність перебудови. Flutter збирає всі брудні елементи за поточний кадр та перебудовує їх пакетно, що оптимізує продуктивність. Після виклику build State повертається до стану чистий.

Властивість widget дозволяє State читати параметри, передані в конструктор StatefulWidget. Оскільки StatefulWidget імутабельний (як StatelessWidget), його поля не змінюються — при зміні параметрів батько створює новий віджет, а State отримує його через didUpdateWidget. Це гарантує, що State завжди працює з актуальними даними батька.

Приклади коду на Dart

Базовий приклад State з полем, що змінюється за таймером. Демонструє initState, setState та dispose:

dart
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:

dart
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 vs StatefulWidget

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.

Чому 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 та не вимагають відмови від стандартного життєвого циклу.

Локальний vs глобальний стан

  • Локальний стан — у State конкретного віджета (позиція скролу, стан фокусу)
  • Глобальний стан — у зовнішньому сховищі (дані користувача, налаштування, кеш)
  • Правило: якщо дані використовуються лише одним віджетом — зберігайте в State
  • Якщо даними користуються 2+ віджети — виносьте в Riverpod/Bloc/Provider

Типові помилки

Перша помилка — забути перевірити 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((){}) змінить список, але екран залишиться тим самим.

Перевірка mounted перед setState

Паттерн безпеки для асинхронних операцій в State:

dart
Future<void> _fetchData() async {
  final data = await ApiService.fetch();
  if (mounted) {
    setState(() => _data = data);
  }
}

Перевірка mounted гарантує, що setState викликається лише для живого State, запобігаючи винятку «setState called after dispose».

Часті запитання

Чим State відрізняється від StatefulWidget?

StatefulWidget — імутабельна конфігурація віджета, а State — змінюваний об'єкт, що зберігає дані та керує життєвим циклом. Віджет може бути перестворений, State — ні. StatefulWidget створює State через createState.

Скільки об'єктів State створюється для одного StatefulWidget?

Рівно один. Метод createState викликається одноразово при першому вбудовуванні StatefulWidget в дерево. Навіть якщо батько перебудовується багаторазово, об'єкт State залишається тим самим, поки не зміниться тип або Key віджета.

Що таке mounted в State?

mounted — булевий прапорець, що показує, чи знаходиться State в дереві віджетів. Після виклику dispose mounted стає false. Використовується для перевірки перед setState в асинхронних колбеках, щоб уникнути винятку.

Чи можна використовувати State без StatefulWidget?

Ні. State завжди прив'язаний до конкретного StatefulWidget через дженерик: State<T extends StatefulWidget>. Створити State напряму, без асоціації з віджетом, архітектурно неможливо.

Що станеться при виклику setState в dispose?

Викинеться виняток: «setState called after dispose». Після виклику dispose State вважається мертвим, і будь-які спроби перебудувати UI через setState заборонені. Рішення — перевіряти mounted перед кожним setState.

Підсумки

  • State — об'єкт управління даними StatefulWidget, що зберігає змінювані поля та ініціює перебудову UI через setState
  • Життєвий цикл включає обов'язкові методи initState, didChangeDependencies, build, didUpdateWidget та dispose, кожен зі своїм призначенням
  • mounted — критичний прапорець безпеки, що запобігає виклику setState після видалення віджета з дерева
  • widget — властивість State для доступу до параметрів пов'язаного StatefulWidget, оновлюване через didUpdateWidget
  • Ізоляція — State не має доступу до інших State; міжвіджетна взаємодія реалізується через InheritedWidget або зовнішні інструменти
  • setState — не викликає build негайно, а лише позначає State як брудний для перебудови в наступному кадрі
  • Правило — використовуйте State для локальних даних віджета; глобальний стан виносьте у зовнішні шари (Riverpod, Bloc)

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також