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

Что такое Dispose?

1.2 Junior🔥 211 комментариев
#Flutter виджеты

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

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

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

Dispose в Flutter: Управление ресурсами

Определение

Dispose — это метод жизненного цикла StatefulWidget, который вызывается при удалении виджета из дерева виджетов. Это место, где мы должны очищать ресурсы, которые мы использовали: закрывать потоки, отписываться от событий, освобождать контроллеры и т.д.

В контексте управления памятью, dispose предотвращает утечки памяти (memory leaks).

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

Widget создан
    ↓
createState() — создание State
    ↓
initState() — инициализация ресурсов
    ↓
build() — построение UI (может вызваться множество раз)
    ↓
...
    ↓
Widget удалён из дерева
    ↓
dispose() — очистка ресурсов ← ВАЖНО!
    ↓
Widget полностью удалён

Практический пример

1. Базовый пример с AnimationController

class CounterWidget extends StatefulWidget {
  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    // Создаём контроллер — это ресурс, требующий очистки
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this, // важно для производительности
    );
    print('Resource allocated');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter: $_counter')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            setState(() => _counter++);
            _controller.forward();
          },
          child: ScaleTransition(
            scale: Tween<double>(begin: 1, end: 1.2).animate(
              CurvedAnimation(parent: _controller, curve: Curves.bounceInOut),
            ),
            child: Text('Tap me'),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    // ✅ ОБЯЗАТЕЛЬНО очищаем ресурс
    _controller.dispose();
    print('Resource cleaned up');
    super.dispose(); // Вызываем в конце!
  }
}

Типичные случаи использования dispose

2. StreamController

class StreamListenerWidget extends StatefulWidget {
  @override
  State<StreamListenerWidget> createState() => _StreamListenerWidgetState();
}

class _StreamListenerWidgetState extends State<StreamListenerWidget> {
  late StreamController<String> _streamController;
  late StreamSubscription<String> _subscription;

  @override
  void initState() {
    super.initState();
    _streamController = StreamController<String>();

    // Подписываемся на события потока
    _subscription = _streamController.stream.listen((data) {
      setState(() {
        print('Received: $data');
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => _streamController.add('Event'),
      child: Text('Send Event'),
    );
  }

  @override
  void dispose() {
    // ✅ Закрываем подписку ПЕРЕД закрытием контроллера
    _subscription.cancel();
    _streamController.close();
    super.dispose();
  }
}

3. TextEditingController

class SearchWidget extends StatefulWidget {
  @override
  State<SearchWidget> createState() => _SearchWidgetState();
}

class _SearchWidgetState extends State<SearchWidget> {
  late TextEditingController _textController;
  late FocusNode _focusNode;

  @override
  void initState() {
    super.initState();
    _textController = TextEditingController();
    _focusNode = FocusNode();

    // Слушаем изменения текста
    _textController.addListener(() {
      print('Text changed: ${_textController.text}');
    });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _textController,
      focusNode: _focusNode,
      decoration: InputDecoration(
        hintText: 'Search...',
        suffixIcon: _textController.text.isNotEmpty
            ? IconButton(
                icon: Icon(Icons.clear),
                onPressed: _textController.clear,
              )
            : null,
      ),
    );
  }

  @override
  void dispose() {
    // ✅ Очищаем в обратном порядке
    _textController.dispose();
    _focusNode.dispose();
    super.dispose();
  }
}

4. Таймер

class TimerWidget extends StatefulWidget {
  @override
  State<TimerWidget> createState() => _TimerWidgetState();
}

class _TimerWidgetState extends State<TimerWidget> {
  late Timer _timer;
  int _seconds = 0;

  @override
  void initState() {
    super.initState();
    // Запускаем таймер каждую секунду
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() => _seconds++);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Timer: $_seconds sec')),
      body: Center(child: Text('$_seconds')),
    );
  }

  @override
  void dispose() {
    // ✅ Отменяем таймер — иначе будет утечка памяти
    _timer.cancel();
    super.dispose();
  }
}

5. Слушатели системных событий

class LifecycleAwareWidget extends StatefulWidget {
  @override
  State<LifecycleAwareWidget> createState() => _LifecycleAwareWidgetState();
}

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

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        print('App resumed');
        break;
      case AppLifecycleState.paused:
        print('App paused');
        break;
      case AppLifecycleState.detached:
        print('App detached');
        break;
      case AppLifecycleState.inactive:
        print('App inactive');
        break;
      case AppLifecycleState.hidden:
        print('App hidden');
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Lifecycle aware widget')),
    );
  }

  @override
  void dispose() {
    // ✅ Удаляем себя из наблюдателей
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}

Утечки памяти и проблемы

❌ НЕПРАВИЛЬНО: забыли dispose

class BadWidget extends StatefulWidget {
  @override
  State<BadWidget> createState() => _BadWidgetState();
}

class _BadWidgetState extends State<BadWidget> {
  late StreamSubscription _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = someStream.listen((_) {
      // Слушаем evento
    });
  }

  @override
  Widget build(BuildContext context) => SizedBox();

  // ❌ Забыли dispose — утечка памяти!
  // Подписка будет оставаться активной даже после удаления виджета
}

✅ ПРАВИЛЬНО: правильно очищаем ресурсы

class GoodWidget extends StatefulWidget {
  @override
  State<GoodWidget> createState() => _GoodWidgetState();
}

class _GoodWidgetState extends State<GoodWidget> {
  late StreamSubscription _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = someStream.listen((_) {
      // Слушаем события
    });
  }

  @override
  Widget build(BuildContext context) => SizedBox();

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose(); // ✅ Всегда в конце!
  }
}

Инструменты отладки

Проверка утечек памяти в DevTools

# Запускаем приложение с отладкой
flutter run --observe

# В другом терминале открываем DevTools
flutter pub global activate devtools
flutter pub global run devtools

# Идём на вкладку Memory и ищем утечки

Чеклист для dispose

// При создании ресурса в initState:
// ✓ AnimationController → _controller.dispose()
// ✓ StreamSubscription → _subscription.cancel()
// ✓ TextEditingController → _controller.dispose()
// ✓ FocusNode → _focusNode.dispose()
// ✓ Timer → _timer.cancel()
// ✓ StreamController → _controller.close()
// ✓ WidgetsBindingObserver → removeObserver(this)

@override
void dispose() {
  _controller.dispose();
  _subscription.cancel();
  WidgetsBinding.instance.removeObserver(this);
  super.dispose(); // ✅ ВСЕГДА в конце!
}

Заключение

Dispose — это критически важный метод для:

  • Предотвращения утечек памяти — освобождение ресурсов
  • Остановки фоновых операций — таймеры, потоки
  • Отписки от событий — слушатели, подписки
  • Правильной очистки контроллеров — animation, text, focus

Основное правило: каждому initState должен соответствовать dispose, где мы очищаем ровно те ресурсы, которые создали.