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

Ответ

В Flutter жизненный цикл можно рассматривать на двух уровнях: жизненный цикл виджета (Widget Lifecycle) и жизненный цикл всего приложения (App Lifecycle).

1. Жизненный цикл StatefulWidget

Это последовательность методов состояния виджета:

  • createState(): Flutter вызывает этот метод для создания объекта State.
  • initState(): Вызывается один раз после createState. Идеальное место для инициализации данных, подписки на Stream или инициализации контроллеров (например, AnimationController, ScrollController).
    @override
    void initState() {
    super.initState();
    _controller = AnimationController(
    vsync: this,
    duration: Duration(seconds: 1),
    );
    _setupData();
    }
  • didChangeDependencies(): Вызывается после initState и при каждом изменении зависимостей виджета (например, когда изменяется объект InheritedWidget, на который подписан этот виджет).
  • build(): Вызывается для отрисовки UI. Может вызываться многократно при setState() или изменениях зависимостей.
  • didUpdateWidget(oldWidget): Вызывается, когда родительский виджет перестраивается и передает новый виджет с тем же runtimeType. Позволяет сравнить старые и новые свойства (oldWidget vs widget).
  • setState(): Не метод жизненного цикла, а вызов, который уведомляет фреймворк об изменении внутреннего состояния и триггерит перестроение виджета через build().
  • deactivate(): Вызывается, когда виджет удаляется из дерева, но может быть позже повторно вставлен в другое место.
  • dispose(): Финальный метод. Вызывается, когда объект State удаляется навсегда. Здесь необходимо освобождать ресурсы: отменять анимации, закрывать потоки, отписываться от листенеров.
    @override
    void dispose() {
    _controller.dispose(); // Важно!
    _streamSubscription.cancel();
    super.dispose();
    }

2. Жизненный цикл приложения (App Lifecycle)

Отслеживается через миксин WidgetsBindingObserver. Состояния:

  • resumed: Приложение видимо и взаимодействует с пользователем.
  • inactive: Приложение неактивно (например, во время входящего звонка на iOS или при переключении между приложениями). Состояние может быть кратковременным.
  • paused: Приложение не видимо пользователю (свернуто или перекрыто другим приложением).
  • detached: Приложение полностью завершено (на некоторых платформах).

Пример использования:

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        print('App resumed: restart animations, refresh data');
        _controller.forward();
        break;
      case AppLifecycleState.paused:
        print('App paused: pause animations, save state');
        _controller.stop();
        _saveCurrentState();
        break;
      // Обработка других состояний
    }
  }
}

Понимание жизненного цикла критично для управления ресурсами, подписками и состоянием UI, что напрямую влияет на производительность и отсутствие утечек памяти.

Ответ 18+ 🔞

А, ну вот, жизненный цикл во Flutter, классика жанра! Это как смотреть на ребёнка: сначала он рождается, потом охуевает от мира, потом делает вид, что работает, а потом накрывается медным тазом. Только тут виджеты, и они не плачут, а крашутся.

В общем, смотри, тут два уровня этой всей движухи: как живёт отдельный виджет и как живёт всё приложение целиком. Разберём по косточкам.

1. Жизнь и смерть StatefulWidget'а

Представь, что ты создаёшь нового персонажа в игре. Вот пошаговая инструкция, как он появляется, мучается и отправляется на свалку истории.

  • createState(): Флаттер такой: «О, новый виджет! Надо создать для него состояние (State)». Всё, персонаж создан, но он ещё в коме.
  • initState(): А вот это уже первый крик младенца! Вызывается один раз, сразу после рождения. Тут самое место, чтобы всё инициализировать: подписаться на стримы, завести анимационные контроллеры, загрузить данные. Доверия ебать ноль к тому, что будет после, поэтому всё важное — сюда.
    @override
    void initState() {
    super.initState();
    _controller = AnimationController( // Создаём контроллер
    vsync: this,
    duration: Duration(seconds: 1),
    );
    _setupData(); // Тянем данные из сети
    }
  • didChangeDependencies(): Вызывается после initState и каждый раз, когда меняется какая-нибудь глобальная хуйня, от которой этот виджет зависит (типа InheritedWidget). Скажем, сменилась тема приложения — виджет об этом узнает здесь.
  • build(): А вот и основная работа! Этот метод вызывается, чтобы нарисовать интерфейс. Его могут дергать до овердохуища раз: при каждом setState(), при изменении зависимостей. Главное — делать его быстрым и чистым, а то пользователь бздеть начнёт от лагов.
  • didUpdateWidget(oldWidget): Ситуация: родительский виджет пересоздался и прислал тебе обновлённую версию самого себя (новый widget). Ты сравниваешь старый (oldWidget) и новый, и если там, например, поменялся id, то ты бежишь перезагружать данные. Подозрение ебать чувствую к этим обновлениям.
  • setState(): Это не метод цикла, а команда «эй, фреймворк, у меня тут данные поменялись, перерисуйся нахуй!». После неё всегда летит build().
  • deactivate(): Виджет выдернули из дерева, но ещё не добили. Его могут взять и воткнуть обратно в другом месте. Редкий зверь, но бывает.
  • dispose(): ФИНАЛ! Виджету пришёл пиздец. Его удаляют навсегда. Здесь категорически важно почистить за собой: остановить анимации, отписаться от стримов, закрыть контроллеры. Иначе будут утечки памяти, и приложение превратится в сосалку для оперативки.
    @override
    void dispose() {
    _controller.dispose(); // Обязательно! Иначе анимация будет жить вечно.
    _streamSubscription.cancel(); // Отписываемся, а то слушать будет пустоту.
    super.dispose();
    }

2. Жизнь всего приложения (App Lifecycle)

А это уже история про то, как твоё приложение живёт в системе: его сворачивают, разворачивают, а иногда и вовсе прибивают. Отслеживается через миксин WidgetsBindingObserver. Состояния такие:

  • resumed: Приложение на переднем плане, активно, пользователь тыкает в него пальцем. Всё работает.
  • inactive: Приложение не в фокусе. Например, на iOS всплыло окно входящего звонка. Состояние краткое, часто сразу переходит в paused.
  • paused: Приложение свернули или перекрыли другим. Не видно, терпения ноль ебать — можно останавливать анимации и видео, чтобы не жрали батарею.
  • detached: Приложение окончательно прикрыли. Встречается не на всех платформах.

Как этим пользоваться? Да вот так, смотри:

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this); // Подписываемся на события жизни
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this); // Отписываемся перед смертью
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        print('App resumed: оживляем анимации, грузим свежие данные');
        _controller.forward(); // Запускаем анимацию снова
        break;
      case AppLifecycleState.paused:
        print('App paused: стопаем всё, сохраняем прогресс');
        _controller.stop(); // Пауза на анимации
        _saveCurrentState(); // Сохраняем игру/данные
        break;
      // Обрабатываем остальные состояния, если надо
    }
  }
}

Короче, если не понимать этот цикл, можно наломать таких дров... Приложение будет бензин хавать как не в себя, тормозить, а память утекать, будто в дырявом ведре. Так что учи матчасть, э бошка думай, прежде чем лепить StreamSubscription без cancel() в dispose.