Когда вызывается метод dispose() у State в Flutter?

Ответ

Метод dispose() вызывается, когда объект State окончательно и безвозвратно удаляется из дерева виджетов. Это точка очистки ресурсов, чтобы избежать утечек памяти.

Конкретные случаи вызова dispose():

  1. При удалении виджета из дерева навсегда (например, Navigator.pushReplacement заменяет текущий экран).
  2. Когда родительский виджет перестраивается и больше не включает данный StatefulWidget.
  3. При использовании GlobalKey и перемещении виджета в другое место дерева с другим ключом.

Что обязательно нужно освобождать в dispose():

class _MyPlayerState extends State<MyPlayer> {
  late AnimationController _animationController;
  late StreamSubscription<int> _timerSubscription;
  Timer? _periodicTimer;
  final _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(vsync: this, duration: Duration(seconds: 1));
    _timerSubscription = Stream.periodic(Duration(seconds: 1)).listen((_) {});
    _periodicTimer = Timer.periodic(Duration(seconds: 1), (_) {});
    _scrollController.addListener(() {});
  }

  @override
  void dispose() {
    // 1. Контроллеры анимаций (обязательно, т.к. они требуют TickerProvider)
    _animationController.dispose();

    // 2. Подписки на стримы
    _timerSubscription.cancel();

    // 3. Таймеры
    _periodicTimer?.cancel();

    // 4. Контроллеры скролла, текста и т.п.
    _scrollController.dispose();

    // Вызов super.dispose() ВСЕГДА в конце
    super.dispose();
  }
}

Важно: После вызова dispose() объект State больше не может быть использован. Любая попытка вызвать setState() после этого выбросит исключение.

Ответ 18+ 🔞

А, ну это же классика, ёпта! Сейчас объясню, как не наступить на эти грабли. Метод dispose() — это такой последний выдох, когда твой State окончательно и навсегда отправляется в утиль. Насовсем, блядь. Не "ой, я его потом верну", а именно накрылся медным тазом. Точка очистки, чтобы память не текла, как решето.

Когда этот похоронный звонок раздаётся:

  1. Виджет выпиливают из дерева навсегда. Типа Navigator.pushReplacement — старый экран в пизду, новый на его место.
  2. Родитель перестроился и решил: "А этого сыночка я сегодня не хочу". И всё, state-объект — в утиль.
  3. Используешь GlobalKey и так намудрил с перемещениями, что виджет получает новый ключ — старый state летит в помойку.

А теперь, что там внутри dispose() нужно придушить, чтобы не бздело потом: Смотри, вот живой пример, чувак:

class _MyPlayerState extends State<MyPlayer> {
  late AnimationController _animationController;
  late StreamSubscription<int> _timerSubscription;
  Timer? _periodicTimer;
  final _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(vsync: this, duration: Duration(seconds: 1));
    _timerSubscription = Stream.periodic(Duration(seconds: 1)).listen((_) {});
    _periodicTimer = Timer.periodic(Duration(seconds: 1), (_) {});
    _scrollController.addListener(() {});
  }

  @override
  void dispose() {
    // 1. Контроллеры анимаций — это святое! Они же тикеры жрут.
    _animationController.dispose();

    // 2. Подписки на стримы — иначе они так и будут висеть, как доверия ебать ноль.
    _timerSubscription.cancel();

    // 3. Таймеры — прибей, иначе будут тикать в пустоту.
    _periodicTimer?.cancel();

    // 4. Контроллеры скролла, текста — тоже ресурсы.
    _scrollController.dispose();

    // И ВСЕГДА, блядь, super.dispose() вызывай в самом конце! Иначе отец не упокоится.
    super.dispose();
  }
}

И главное, запомни раз и навсегда: после того как dispose() отработал, твой state — это труп. Мёртвенький. Любая попытка ткнуть в него пальцем, типа вызвать setState(), закончится дикой ошибкой. Сам от себя охуеешь, когда приложение крешнется на ровном месте. Так что чисти за собой, не будь распиздяем.