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

Когда вызывается state dispose?

1.0 Junior🔥 281 комментариев
#Flutter виджеты#State Management

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Метод dispose() — это критически важный метод в жизненном цикле StatefulWidget'а. Понимание его вызова необходимо для предотвращения утечек памяти и правильного управления ресурсами.

Основное назначение dispose()

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

class MyScreen extends StatefulWidget {
  @override
  State<MyScreen> createState() => MyScreenState();
}

class MyScreenState extends State<MyScreen> {
  late AnimationController _animationController;
  late StreamSubscription _subscription;
  
  @override
  void initState() {
    super.initState();
    // Инициализация ресурсов
    _animationController = AnimationController(
      duration: Duration(seconds: 1),
      vsync: this,
    );
    
    _subscription = someStream.listen((event) {
      print('Event: $event');
    });
  }
  
  @override
  void dispose() {
    // Очистка ресурсов
    _animationController.dispose();
    _subscription.cancel();
    super.dispose(); // Всегда вызывай super.dispose() в конце!
  }
  
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Когда именно вызывается dispose()

1. Когда виджет удаляется из дерева

Самый очевидный случай — когда пользователь навигирует с экрана, на котором находится StatefulWidget:

// В первом экране
class FirstScreen extends StatefulWidget {
  @override
  State<FirstScreen> createState() => FirstScreenState();
}

class FirstScreenState extends State<FirstScreen> {
  @override
  void dispose() {
    print('FirstScreen disposed'); // Выполнится при навигации
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        Navigator.push(
          context,
          MaterialPageRoute(builder: (_) => SecondScreen()),
        );
        // dispose() вызовется здесь
      },
      child: Text('Go to Second'),
    );
  }
}

2. Когда родитель перестраивается и удаляет дочерний виджет

Если родитель условно включает/исключает виджет:

class ParentWidget extends StatefulWidget {
  @override
  State<ParentWidget> createState() => ParentWidgetState();
}

class ParentWidgetState extends State<ParentWidget> {
  bool showChild = true;
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () => setState(() => showChild = !showChild),
          child: Text('Toggle Child'),
        ),
        if (showChild) ChildWidget(), // dispose() вызовется при удалении
      ],
    );
  }
}

class ChildWidget extends StatefulWidget {
  @override
  State<ChildWidget> createState() => ChildWidgetState();
}

class ChildWidgetState extends State<ChildWidget> {
  @override
  void dispose() {
    print('ChildWidget disposed'); // Выполнится при showChild = false
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Text('Child');
  }
}

3. При замене типа виджета

Если виджет заменяется другим типом виджета:

class DynamicWidget extends StatefulWidget {
  final bool useWidget1;
  const DynamicWidget({required this.useWidget1});
  
  @override
  State<DynamicWidget> createState() => DynamicWidgetState();
}

class DynamicWidgetState extends State<DynamicWidget> {
  @override
  void didUpdateWidget(DynamicWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.useWidget1 != widget.useWidget1) {
      // dispose() не вызывается здесь
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return widget.useWidget1 ? WidgetOne() : WidgetTwo();
  }
}

4. При завершении приложения

Когда приложение закрывается, все активные State'ы получают dispose():

@override
void dispose() {
  print('App is closing'); // Выполнится при выходе из приложения
  super.dispose();
}

Что необходимо очищать в dispose()

1. Controllers (Animation, TextEdit, ScrollView)

late AnimationController _animationController;
late TextEditingController _textController;
late ScrollController _scrollController;

@override
void dispose() {
  _animationController.dispose();
  _textController.dispose();
  _scrollController.dispose();
  super.dispose();
}

2. Streams и Subscriptions

late StreamSubscription _subscription;

@override
void initState() {
  super.initState();
  _subscription = Firestore.instance.collection('users').snapshots().listen((_) {});
}

@override
void dispose() {
  _subscription.cancel();
  super.dispose();
}

3. Таймеры

late Timer _timer;

@override
void initState() {
  super.initState();
  _timer = Timer.periodic(Duration(seconds: 1), (_) {
    print('Tick');
  });
}

@override
void dispose() {
  _timer.cancel();
  super.dispose();
}

4. Listeners

late FocusNode _focusNode;

@override
void initState() {
  super.initState();
  _focusNode = FocusNode();
  _focusNode.addListener(_onFocusChange);
}

void _onFocusChange() {
  print('Focus changed');
}

@override
void dispose() {
  _focusNode.removeListener(_onFocusChange);
  _focusNode.dispose();
  super.dispose();
}

5. Providers (для пакета provider)

@override
void dispose() {
  context.read<UserProvider>().removeListener(_onUserChanged);
  super.dispose();
}

Последствия неправильного использования dispose()

Утечка памяти

// Плохо — нет очистки
class BadWidget extends StatefulWidget {
  @override
  State<BadWidget> createState() => BadWidgetState();
}

class BadWidgetState extends State<BadWidget> {
  late Timer _timer;
  
  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(Duration(seconds: 1), (_) {}); // Таймер никогда не отменяется
  }
  
  @override
  Widget build(BuildContext context) => Container();
  // dispose() отсутствует — УТЕЧКА ПАМЯТИ!
}

Ошибки при биндинге к disposed объекту

// Плохо
_controller.addListener(() {
  // После dispose может быть вызвано
  setState(() {}); // "setState called after dispose" error
});

Лучшие практики

1. Всегда вызывай super.dispose()

@override
void dispose() {
  _animationController.dispose();
  _textController.dispose();
  super.dispose(); // ОБЯЗАТЕЛЬНО в конце!
}

2. Используй mounted для проверки

@override
void dispose() {
  if (mounted) {
    // Выполни только если State ещё активен
    setState(() {});
  }
  super.dispose();
}

3. Избегай async операций в dispose()

// Плохо
@override
void dispose() {
  await repository.saveData(); // Может не завершиться
  super.dispose();
}

// Хорошо — используй didChangeAppLifecycleState
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  if (state == AppLifecycleState.detached) {
    repository.saveData();
  }
}

4. Создавай dispose-able классы

class MyService {
  void dispose() {
    // Очистка
  }
}

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => MyWidgetState();
}

class MyWidgetState extends State<MyWidget> {
  late MyService _service;
  
  @override
  void initState() {
    super.initState();
    _service = MyService();
  }
  
  @override
  void dispose() {
    _service.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) => Container();
}

Итог

dispose() вызывается когда:
  • StatefulWidget удаляется из дерева виджетов
  • Родитель перестраивается и исключает виджет
  • Приложение завершается

В dispose() нужно:

  • Отменять все таймеры
  • Закрывать потоки и subscription'ы
  • Вызывать dispose() на controllera
  • Удалять listener'ы
  • Освобождать другие ресурсы

Всегда вызывай super.dispose() в конце! Это критически важно для правильной очистки ресурсов и предотвращения утечек памяти.