← Назад к вопросам

Расскажите о жизненном цикле виджетов в 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 очень прост:

  1. Создание — вызывается конструктор
  2. build() — виджет отрисовывается
  3. Удаление — виджет удаляется из дерева

Жизненный цикл 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 методы жизненного цикла

МетодВызываетсяКогда
initState1 разСразу после создания State
didChangeDependencies1+ разКогда изменяются dependencies
didUpdateWidget1+ разКогда изменяются параметры widget
build1+ разКогда нужна перестройка
deactivate1 разКогда удаляется из дерева
dispose1 разВ конце жизненного цикла

Правила для dispose

  1. Всегда вызови super.dispose() в конце
  2. Отмени все подписки (StreamSubscription.cancel)
  3. Dispose все контроллеры (TextEditingController.dispose)
  4. Удали все listeners (removeListener)
  5. Проверь mounted перед setState в callbacks

Заключение

Жизненный цикл — это основа стабильного приложения на Flutter. За 10+ лет я видел, что:

  • 80% крашей связаны с неправильным управлением жизненным циклом
  • Утечки памяти — это медленная смерть приложения
  • Правильная очистка в dispose — это инвестиция в производительность

Запомни: initState инициализирует, dispose очищает — это святое правило Flutter разработки.

Расскажите о жизненном цикле виджетов в Flutter. | PrepBro