Расскажи про жизненный цикл StatefulWidget

Ответ

Жизненный цикл StatefulWidget управляется его ассоциированным объектом State. Основные этапы:

  1. createState(): Вызывается фреймворком при вставке виджета в дерево. Создает экземпляр класса State.
  2. initState(): Вызывается один раз сразу после createState(). Идеальное место для инициализации данных, подписки на Stream'ы или создания контроллеров (например, AnimationController, ScrollController).
    @override
    void initState() {
      super.initState();
      _animationController = AnimationController(
        vsync: this,
        duration: const Duration(seconds: 1),
      );
      _dataStreamSubscription = myService.dataStream.listen(_updateData);
    }
  3. didChangeDependencies(): Вызывается после initState() и при изменении зависимостей виджета (например, когда обновился InheritedWidget, на который он подписан через context.dependOnInheritedWidgetOfExactType).
  4. build(): Вызывается многократно для построения UI в ответ на setState() или изменения внешних данных. Должен быть чистой функцией от состояния (State) и свойств (widget.someProp).
  5. didUpdateWidget(oldWidget): Вызывается, когда родительский виджет перестраивается и создает новый экземпляр StatefulWidget с тем же runtimeType, но этот State объекта переиспользуется. Позволяет сравнивать старые и новые свойства (oldWidget vs widget).
    @override
    void didUpdateWidget(MyWidget oldWidget) {
      super.didUpdateWidget(oldWidget);
      if (widget.itemId != oldWidget.itemId) {
        _loadNewItem(widget.itemId);
      }
    }
  6. setState(): Метод для уведомления фреймворка об изменении внутреннего состояния. Вызывает перестроение виджета через build().
  7. deactivate(): Вызывается, когда State удаляется из дерева, но может быть повторно вставлен в другое место дерева (например, внутри GlobalKey).
  8. dispose(): Вызывается, когда State объект удаляется навсегда. Критически важно освобождать ресурсы: отменять таймеры, закрывать контроллеры, отписываться от Stream'ов.
    @override
    void dispose() {
      _animationController.dispose(); // Обязательно!
      _dataStreamSubscription.cancel();
      super.dispose();
    }

Важно: После вызова dispose(), свойство mounted становится false. Любой последующий вызов setState() выбросит исключение.

Ответ 18+ 🔞

А, ну вот, жизненный цикл StatefulWidget, ёпта! Это как инструкция по сборке шкафа из Икеи, только если её не прочитать, приложение не просто криво стоит, а накрывается медным тазом. Слушай сюда, сейчас разжую.

Представь, что твой StatefulWidget — это новый сосед, который заехал в квартиру. Сначала фреймворк его createState() вызывает — это типа выдали ключи и сказали: «Вот твоя хата, живи». Создаётся объект State, и он такой стоит посреди пустой комнаты и охуевает.

Дальше сразу initState() зовут. Это святое место, чувак! Тут надо всё инициализировать, пока никто не видит. Создал AnimationController? Отлично. Подписался на какой-нибудь стрим данных? Идеально. Главное — super.initState() не забудь позвать, а то предки обидятся. Вот пример, чтобы не быть распиздяем:

@override
void initState() {
  super.initState();
  _animationController = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 1),
  );
  _dataStreamSubscription = myService.dataStream.listen(_updateData);
}

Потом может прилететь didChangeDependencies(). Это когда твой виджет вдруг вспоминает, что он зависит от какой-то общей фигни вроде InheritedWidget (ну, типа Theme или MediaQuery), и эта фигня обновилась. Он такой: «Опа, а у меня тут зависимости поменялись, надо бы перепроверить».

А потом начинается самое весёлое — build(). Его вызывают дохуища раз. Каждый раз, когда ты кричишь setState(), или когда свыше приходят новые данные. Этот метод должен быть чистой функцией, как слеза младенца. Только смотри на своё внутреннее состояние и на widget.someProp, который прилетел снаружи, и рисуй интерфейс. Никаких посторонних действий тут, ядрёна вошь!

Дальше может случиться didUpdateWidget(oldWidget). Это когда твой родительский виджет пересобрался и прислал тебе нового «соседа по квартире» (новый экземпляр StatefulWidget), но твой старый объект State решают оставить жить. Ты должен сравнить старого и нового соседа. Если, например, поменялся itemId, то надо загрузить новые данные, а не сидеть на старых.

@override
void didUpdateWidget(MyWidget oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (widget.itemId != oldWidget.itemId) {
    _loadNewItem(widget.itemId); // Грузим новую хрень, а не старую
  }
}

Ну и король всего этого балагана — setState(). Это ты кричишь фреймворку: «Эй, пацаны, у меня тут состояние поменялось, давайте перерисуем эту херовину!». После этого вызовется build(), и всё обновится. Волшебство, блядь.

А теперь про грустное. deactivate() — это когда тебя временно выселяют из квартиры, но ключи не забирают. Может, потом впихнут в другое место дерева (спасибо GlobalKey). А вот dispose() — это уже полный пиздец. Тебе приносят повестку, что квартира продана, и ты нахуй больше не нужен. Здесь критически важно всё за собой прибрать! Убить все контроллеры, отписаться от всех стримов, отменить все таймеры. Если этого не сделать, будут утечки памяти, и приложение начнёт лагать и жрать батарею как не в себя.

@override
void dispose() {
  _animationController.dispose(); // Обязательно, а то будет течь!
  _dataStreamSubscription.cancel();
  super.dispose();
}

И запомни, чувак, главное правило: после того как вызвали dispose(), флаг mounted падает в false. И если ты после этого попробуешь вызвать setState(), то получишь исключение прямо в ебало. Так что не суйся, когда уже всё кончено. Вот такая, блядь, философия.