← Назад к вопросам
Расскажите о жизненном цикле виджетов в Flutter.
2.3 Middle🔥 221 комментариев
#Flutter виджеты#State Management
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Жизненный цикл виджетов в Flutter
Понимание жизненного цикла — это фундамент эффективной разработки на Flutter. Неправильное управление жизненным циклом приводит к утечкам памяти, багам и падениям приложения.
Два типа виджетов
StatelessWidget
- Неизменяемый
- Не имеет состояния
- Создаётся один раз и больше не изменяется
StatefulWidget
- Изменяемый
- Имеет состояние (State)
- Может быть перестроен при изменении состояния
Жизненный цикл StatelessWidget
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Это единственный метод, который вызывается
// Вызывается один раз при создании виджета
// Вызывается снова, если parent widget перестроился
return Container();
}
}
Жизненный цикл StatelessWidget очень прост:
- Создание — вызывается конструктор
- build() — виджет отрисовывается
- Удаление — виджет удаляется из дерева
Жизненный цикл StatefulWidget
┌─────────────────────────────────────────┐
│ StatefulWidget Constructor │
│ (createState) │
└────────────────┬────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ State Created │
└────────────────┬────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ initState() │
│ (вызывается один раз) │
└────────────────┬────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ didChangeDependencies() │
│ (при изменении dependencies) │
└────────────────┬────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ build() │
│ (вызывается при setState, didChange...) │
└────────────────┬────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ деактивация / удаление │
│ deactivate() → dispose() │
└─────────────────────────────────────────┘
Подробный пример
class MyStatefulWidget extends StatefulWidget {
final String title;
const MyStatefulWidget({required this.title, Key? key}) : super(key: key);
@override
State<MyStatefulWidget> createState() {
print('1. createState() - создание State объекта');
return _MyStatefulWidgetState();
}
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counter = 0;
@override
void initState() {
super.initState();
print('2. initState() - инициализация, вызывается один раз');
// Инициализируй controllers, listeners, subscriptions
// Запроси данные с API
// Установи initial values
_loadData();
}
Future<void> _loadData() async {
print(' → Загрузка данных из API');
await Future.delayed(Duration(seconds: 1));
print(' → Данные загружены');
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('3. didChangeDependencies() - при изменении dependencies');
// Используется когда виджет зависит от InheritedWidget
// Например, Theme или MediaQuery
final theme = Theme.of(context);
print(' → Текущая тема: ${theme.brightness}');
}
@override
void didUpdateWidget(MyStatefulWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print('4. didUpdateWidget() - при изменении параметров widget');
if (oldWidget.title != widget.title) {
print(' → Title changed from ${oldWidget.title} to ${widget.title}');
}
}
@override
Widget build(BuildContext context) {
print('5. build() - отрисовка UI');
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Счётчик: $_counter'),
ElevatedButton(
onPressed: () {
setState(() {
_counter++;
print('6. setState() - запрос перестроения');
});
},
child: Text('Увеличить'),
),
],
),
),
);
}
@override
void deactivate() {
print('7. deactivate() - виджет удаляется из дерева');
super.deactivate();
// Хорошее место для очистки подписок
// но не обязательно удаляй все ресурсы
}
@override
void dispose() {
print('8. dispose() - освобождение ресурсов (ПОСЛЕДНИЙ вызов)');
super.dispose();
// ✅ ОБЯЗАТЕЛЬНО очисти здесь:
// - Закрой streams и subscriptions
// - Dispose controllers
// - Отмени http requests
// - Удали listeners
}
}
Вывод консоли при создании
1. createState()
2. initState()
3. didChangeDependencies()
5. build()
Вывод при setState()
5. build()
Вывод при удалении
7. deactivate()
8. dispose()
Практический пример: API запрос
class UserDetailScreen extends StatefulWidget {
final String userId;
const UserDetailScreen({required this.userId});
@override
State<UserDetailScreen> createState() => _UserDetailScreenState();
}
class _UserDetailScreenState extends State<UserDetailScreen> {
late Future<User> _userFuture;
StreamSubscription<User>? _userSubscription;
@override
void initState() {
super.initState();
// Инициализируй Future один раз
_userFuture = _fetchUser(widget.userId);
// Подпишись на обновления пользователя
_userSubscription = userStream.listen((user) {
if (mounted) {
setState(() {});
}
});
}
Future<User> _fetchUser(String userId) async {
return await userRepository.getUser(userId);
}
@override
void didUpdateWidget(UserDetailScreen oldWidget) {
super.didUpdateWidget(oldWidget);
// Если userId изменился, загрузи нового пользователя
if (oldWidget.userId != widget.userId) {
_userFuture = _fetchUser(widget.userId);
}
}
@override
void dispose() {
// Очисти подписку
_userSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<User>(
future: _userFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
final user = snapshot.data!;
return Text(user.name);
},
);
}
}
Утечки памяти и как их избежать
❌ ПЛОХО: утечка памяти
class BadExample extends StatefulWidget {
@override
State<BadExample> createState() => _BadExampleState();
}
class _BadExampleState extends State<BadExample> {
late StreamSubscription _subscription;
@override
void initState() {
super.initState();
// Подписываешься на stream
_subscription = myStream.listen((data) {
setState(() {});
});
}
@override
void dispose() {
// ❌ Забыл отписаться!
super.dispose();
}
}
✅ ПРАВИЛЬНО: без утечки
class GoodExample extends StatefulWidget {
@override
State<GoodExample> createState() => _GoodExampleState();
}
class _GoodExampleState extends State<GoodExample> {
late StreamSubscription _subscription;
@override
void initState() {
super.initState();
_subscription = myStream.listen((data) {
if (mounted) { // Проверь, что виджет всё ещё в дереве
setState(() {});
}
});
}
@override
void dispose() {
_subscription.cancel(); // ✅ Очистил подписку
super.dispose();
}
}
Key методы жизненного цикла
| Метод | Вызывается | Когда |
|---|---|---|
| initState | 1 раз | Сразу после создания State |
| didChangeDependencies | 1+ раз | Когда изменяются dependencies |
| didUpdateWidget | 1+ раз | Когда изменяются параметры widget |
| build | 1+ раз | Когда нужна перестройка |
| deactivate | 1 раз | Когда удаляется из дерева |
| dispose | 1 раз | В конце жизненного цикла |
Правила для dispose
- Всегда вызови super.dispose() в конце
- Отмени все подписки (StreamSubscription.cancel)
- Dispose все контроллеры (TextEditingController.dispose)
- Удали все listeners (removeListener)
- Проверь mounted перед setState в callbacks
Заключение
Жизненный цикл — это основа стабильного приложения на Flutter. За 10+ лет я видел, что:
- 80% крашей связаны с неправильным управлением жизненным циклом
- Утечки памяти — это медленная смерть приложения
- Правильная очистка в dispose — это инвестиция в производительность
Запомни: initState инициализирует, dispose очищает — это святое правило Flutter разработки.