StatefulWidget — уиджет Flutter с променливо състояние, позволяващ на UI да реагира на действия на потребителя, асинхронни събития и потоци от данни. Според официалната документация на Flutter (Flutter.dev, 2026), StatefulWidget се използва за всички интерактивни елементи на приложението: формуляри за въвеждане, анимации, квадратчета за отметка, превключватели и екрани, зареждащи данни от мрежата. За разлика от StatelessWidget, той създава отделен обект State, който се запазва през целия жизнен цикъл и може да бъде преизграден без повторно създаване на самия уиджет.
Основни точки
StatefulWidget — е клас Flutter, който може да променя състоянието си в отговор на действия на потребителя, системни събития или асинхронни операции. За разлика от StatelessWidget, StatefulWidget не се показва директно — той създава обект State, който отговаря за рендерирането. Разделянето на два класа (Widget и State) позволява на Flutter да преизгражда UI без повторно създаване на самия уиджет, което дава значително предимство в производителността при чести актуализации.
Архитектурата на StatefulWidget следва модела «разделяне на променливото и непроменливото»: самият уиджет остава непроменлив (като StatelessWidget), а цялото променливо състояние се съхранява в отделен обект State. Това позволява на Flutter да преизползва уиджети, като ги сравнява по тип и Key, и в същото време да запазва актуалното състояние между преизгражданията.
Според Google (Flutter Architectural Overview, 2026), StatefulWidget е оптимален за сценарии, при които състоянието се променя повече от веднъж през живота на уиджета: текстови полета, анимации, таймери, потоци от данни, асинхронни зареждания. За еднократна инициализация е достатъчен StatelessWidget.
StatefulWidget е задължителен, когато уиджетът трябва да реагира на външни събития: натискане на бутон, завършване на HTTP заявка, актуализация на данни от база данни, абонамент за WebSocket. Той е необходим също за уиджети с анимация, текстови полета с контролери и компоненти, управляващи фокуса. Ако уиджетът само показва данни и не генерира събития — използвайте StatelessWidget.
StatefulWidget се състои от два класа: самия StatefulWidget (лек, непроменлив) и State (тежък, променлив). Фреймворкът създава State чрез метода createState(), който се извиква еднократно при вграждане в дървото. State получава референция към уиджета чрез свойството widget и може да достъпва неговите полета по всяко време на жизнения цикъл.
Жизненият цикъл на StatefulWidget се състои от шест основни етапа, всеки от които предоставя метод за презаписване за изпълнение на специфични задачи. Разбирането на тези етапи е критично важно за правилната работа с ресурси и избягване на изтичане на памет.
createState — първият метод на жизнения цикъл, извикван при вграждане на StatefulWidget в дървото. Трябва да върне нова инстанция на State, свързана с този уиджет. Този метод се извиква точно веднъж през целия живот на елемента. Важно е да не се изпълняват тежки операции тук — createState трябва да бъде максимално лек.
initState — извиква се веднага след създаването на State, преди първото изграждане на UI. Тук се изпълняват: инициализация на контролери (TextEditingController, AnimationController), абонамент за потоци от данни (StreamSubscription), настройка на таймери и начална инициализация на полета. Според Flutter doc (Flutter.dev, 2026) в initState не може да се извиква BuildContext.of() — дървото все още не е напълно монтирано.
didChangeDependencies — извиква се след initState и всеки път, когато се променят зависимостите на InheritedWidget. Това е подходящото място за извикване на MediaQuery.of(context) или абонамент за Theme — стойности, които могат да се променят по време на работа на приложението. Ако уиджетът използва InheritedWidget, логиката за инициализация трябва да е тук, а не в initState.
build — основният метод, който връща дървото от уиджети. Извиква се след initState, след didChangeDependencies и след всеки setState. didUpdateWidget се извиква, когато родителят преизгражда и предава StatefulWidget с нови параметри. Тук можете да сравните старите и новите полета на уиджета и, ако е необходимо, да актуализирате състоянието.
dispose — заключителният етап на жизнения цикъл. Тук се освобождават всички ресурси: отписване от потоци, премахване на контролери, отмяна на таймери. Неизвикването на dispose води до изтичане на памет. След dispose State се счита за мъртъв — извикването на setState вътре в него хвърля изключение.
Механизмът на работа на StatefulWidget се основава на координираната работа на три същности: Widget (леко описание), Element (междинен слой) и State (хранилище за данни). Когато Flutter срещне StatefulWidget в описанието, той създава StatefulElement, който извиква createState и съхранява референция към обекта State. При преизграждане на родителя, Flutter сравнява новия уиджет с текущия Element — ако типът и Key съвпадат, Element се актуализира, а State остава същият.
Състоянието се променя само чрез извикване на setState, който уведомява фреймворка за необходимостта от преизграждане. Важно е да се разбере: setState не променя автоматично състоянието — той само маркира уиджета като «мръсен». Разработчикът сам актуализира полетата на State в callback-а, предаден на setState. След завършване на callback-а, Flutter извиква build и актуализира UI.
Според екипа на Dart/Flutter (Dart Language Specification, 2026), това разделение гарантира, че всички промени на състоянието се случват синхронно преди извикването на build, изключвайки ситуацията, при която UI показва частично актуализирани данни. Това е ключовият механизъм за консистентност на интерфейса във Flutter.
Нека разгледаме прост StatefulWidget — брояч на кликвания на бутон. Той демонстрира основния модел: създаване на State, инициализация на поле в initState, промяна чрез setState:
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Брой: $_count'),
ElevatedButton(
onPressed: _increment,
child: const Text('Увеличи'),
),
],
);
}
}
Пример с асинхронно зареждане на данни и управление на жизнения цикъл. StatefulWidget зарежда данни от мрежата и показва състоянието на зареждане:
class UserProfilePage extends StatefulWidget {
final String userId;
const UserProfilePage({super.key, required this.userId});
@override
State<UserProfilePage> createState() => _UserProfilePageState();
}
class _UserProfilePageState extends State<UserProfilePage> {
UserModel? _user;
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
final user = await UserService.fetchUser(widget.userId);
setState(() {
_user = user;
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
if (_isLoading) return const CircularProgressIndicator();
return Text('Здравей, ${_user!.name}');
}
}
Във втория пример е важно да се отбележи: initState стартира асинхронна операция, но самият метод не е асинхронен. Асинхронността се реализира чрез async/await вътре в отделния метод _loadUser, който актуализира състоянието чрез setState след завършване на заявката. Този подход гарантира, че уиджетът ще покаже правилно индикатора за зареждане преди получаване на данните.
Изборът между StatefulWidget и StatelessWidget не е само въпрос на наличие на състояние. StatefulWidget предоставя пълен жизнен цикъл с методите initState, didChangeDependencies, didUpdateWidget и dispose, което е необходимо за работа с контролери, анимации и потоци. StatelessWidget, от своя страна, няма тези методи и винаги е по-лек за фреймворка.
Препоръка на екипа на Flutter (Flutter docs, 2026) — минимизирайте броя StatefulWidget в приложението, като повдигате състоянието нагоре в дървото (State Hoisting) или използвате решения за управление на състоянието (Riverpod, Bloc, Provider). Всеки StatefulWidget създава обект State, който живее до премахване на елемента — колкото повече такива уиджети, толкова по-голямо е натоварването на паметта.
| Критерий | StatefulWidget | StatelessWidget |
|---|---|---|
| Състояние | Променливо | Непроменливо |
| Жизнен цикъл | 6 етапа | Само build |
| Обект State | Създава се отделно | Не е необходим |
| setState | Достъпен | Недостъпен |
| Абонаменти | initState/dispose | Не се поддържат |
| const конструктор | Ограничен | Напълно поддържан |
| Консумация на памет | По-висока | По-ниска |
StatefulWidget изисква повече ресурси от StatelessWidget поради необходимостта от създаване и поддържане на обекта State. Въпреки това, правилното използване на StatefulWidget не води до проблеми с производителността, ако се спазват няколко правила. Първо, избягвайте дълбоко влагане на StatefulWidget — всяко ниво добавя допълнително натоварване за обхождане на дървото. Второ, разделяйте сложния StatefulWidget на няколко прости уиджета, всеки отговорен за своята част от състоянието.
Според изследване на Flutter Performance (Flutter.dev, февруари 2026), най-честата причина за спад на FPS е извикването на setState в родителския уиджет, който преизгражда всички потомци, включително StatelessWidget, които не са променили своя изглед. Решението — преместете променливата част на UI в отделен StatefulWidget, така че setState да преизгражда само минимума от необходимите уиджети.
Използването на const вътре в State е друга важна техника. Ако дъщерните уиджети са декларирани като const, Flutter няма да ги преизгражда при извикване на setState в родителя. Това намалява натоварването на фреймворка и съкращава времето за рендериране на кадъра.
Всяко извикване на setState задейства пълно преизграждане на уиджета. Ако състоянието се променя с висока честота (например анимация или поток от данни), обмислете използването на AnimatedBuilder, ValueListenableBuilder или StreamBuilder вместо ръчно извикване на setState. Тези уиджети оптимизират преизграждането, актуализирайки само онази част от UI, която действително се е променила.
Първата типична грешка с StatefulWidget — извикване на setState след dispose. Когато уиджетът е премахнат от дървото, State се счита за мъртъв и всяко извикване на setState хвърля изключението «setState called after dispose». Най-често това се случва, когато асинхронна операция завърши след премахване на уиджета. Решението — проверявайте флага mounted преди извикване на setState или отменяйте асинхронните операции в dispose.
Втора грешка — изпълнение на тежки изчисления в метода build. Тъй като build се извиква при всеки setState и при всяко преизграждане на родителя, всички изчисления трябва да бъдат максимално леки. Ако трябва да изпълните ресурсоемка операция — преместете я в отделен Isolate или кеширайте резултата в поле на State.
Трета грешка — липса на извикване на super.initState() и super.dispose(). При презаписване на тези методи, разработчикът е длъжен да извика родителската имплементация. Ако това не се направи, фреймворкът няма да може правилно да управлява състоянието на Element, което ще доведе до трудно откриваеми грешки.
mounted преди setState в асинхронни callback-иsuper.initState() и super.dispose()Често задавани въпроси
StatefulWidget може да променя състоянието си чрез setState, има жизнен цикъл (initState, dispose) и създава отделен обект State. StatelessWidget не може да променя състояние и няма методи на жизнения цикъл — той просто показва подадените данни.
createState се извиква точно веднъж за всяка инстанция на StatefulElement. Дори ако родителят преизгражда многократно, докато типът и Key на уиджета не се променят, createState не се извиква — използва се съществуващият обект State.
Ресурсите няма да бъдат освободени: контролерите ще продължат да работят на заден план, абонаментите за потоци ще останат активни, таймерите няма да бъдат отменени. Това води до изтичане на памет и може да причини извикване на setState след dispose, което хвърля изключение.
Да, конструкторът на StatefulWidget може да бъде const. Това обаче не дава същата полза като за StatelessWidget — обектът State все пак ще бъде създаден при първото вграждане. const засяга само самия уиджет (леката обвивка), не и State.
didUpdateWidget се извиква, когато родителят предава StatefulWidget с нови параметри. Това е необходимо за синхронизиране на състоянието с нови данни — например, ако userId се промени в параметрите, трябва да се зареди профилът на новия потребител.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също