Stateful. Жизненный цикл

Схема жизненного цикла

В отличие от StatelessWidget, который неизменяем, StatefulWidget может изменять своё состояние в процессе работы. Это делает его более гибким, но также требует понимания жизненного цикла, чтобы правильно управлять ресурсами и обновлять интерфейс.

Жизненный цикл StatefulWidget можно представить как несколько последовательных этапов, которые вызываются в определённом порядке.

Схема жизненного цикла

Основные методы жизненного цикла:

  1. StatefulWidget → прописали код виджета
  2. Constructor → вызвали конструктор
  3. createState() → создаёт объект состояния State
  4. State получает context → Информация о местоположении виджета в дереве
  5. initState() → вызывается один раз когда создаётся объект (добавляется в дерево)
  6. didCnangeDependecies → вызывается, когда изменяются зависимости виджета
  7. build() → строит (отрисовывает) виджет
  8. setState() → сообщает, что состояние изменилось, и необходимо перерисовать UI
  9. didUpdateWidget() → вызывается, если виджет обновился.
  10. deactivate() → удаление из дерева, но есть возможность вернуть виджет в него
  11. dispose() → окончательное удаление виджета из дерева

Подробное описание работы жизненного цикла виджета

  1. Когда BuildContext назначается виджету, он считается "смонтированным" (mounted). Это означает, что у State-объекта устанавливается флаг mounted = true, и фреймворк знает, что виджет присутствует в дереве.

    • Этот флаг можно использовать, чтобы проверить, можно ли обновлять состояние (например, перед вызовом setState()).
  2. Вызывается initState(). Это первый метод, вызываемый при создании State. Здесь можно инициализировать ресурсы, подписаться на Stream, создать AnimationController и т. д.

    • Аналогии: onCreate() в Android, viewDidLoad() в iOS.
    • Важно: initState() вызывается только один раз за время жизни State.
  3. Вызывается didChangeDependencies().

    • Этот метод вызывается сразу после initState(), если State- объект зависит от InheritedWidget.
    • Если изменяется InheritedWidget, от которого зависит State, didChangeDependencies() сработает снова.
    • Важно: Метод может вызываться несколько раз за время жизни State.
  4. Вызывается build(). Это главный метод отрисовки UI. Он вызывается каждый раз, когда фреймворк считает, что виджет требует обновления.

    • Каждый виджет в дереве рекурсивно вызывает build(), поэтому этот метод должен работать максимально быстро.
    • Нельзя делать в build():
      • Тяжелые вычисления (их нужно выполнять асинхронно и сохранять результат в State).
      • Запросы в сеть (иначе UI зависнет).
  5. Вызывается didUpdateWidget(), если родительский виджет изменился.

    • Если StatefulWidget пересоздан, но State остался тем же, вызывается didUpdateWidget().
    • В этот момент передается oldWidget, чтобы можно было сравнить старые и новые свойства.
    • Используется, например, для перезапуска анимации, если изменился параметр анимации.
  6. Когда вызывается setState(), виджет помечается как "грязный" (dirty).

    • Это сигнал фреймворку, что виджет требует перерисовки.
    • После этого build() будет вызван снова.
  7. Если виджет удаляется из дерева, вызывается deactivate().

    • Но объект State может быть повторно использован в другом месте дерева.
    • Пример: при использовании Navigator.push() и Navigator.pop() виджет может временно исчезнуть и снова появиться.
  8. Когда виджет окончательно удаляется, вызывается dispose().

    • Этот метод используется для освобождения ресурсов:
      • Отмена подписок на Stream.
      • Очистка контроллеров (AnimationController, TextEditingController).
      • Остановка таймеров и других асинхронных операций.
    • После dispose() объект State уничтожается, а mounted становится false.

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

Важно!

  • Внутри StatefulWidget нет доступа до Context и до State
  • Внутри State есть доступ до Context и до State
  • Внутри State нет прямого доступа к свойствам StatefulWidget
  • Для доступа к свойствам StatefulWidget используется ключевое слово widget
Файл widgets/lifecycle_example.dart
Светлая тема Темная тема
import 'package:flutter/material.dart';  
  
class ListOfSomeWidget extends StatefulWidget {  
  @override  
  State<ListOfSomeWidget> createState() => _MyAppState();  
}  
  
class _MyAppState extends State<ListOfSomeWidget> {  
  // Так сделано для примера, чтобы посмотреть  
  // на методы жизненного цикла при добавлении удалении и обновлении виджета  List<Widget> listOfWidget = [];  
  int index = 0;  
  
  void deleteItem() => setState(() => listOfWidget.removeLast());  
  void insertItem() => setState(() => listOfWidget.add(LifeCycleExample()));  
  void updateItem() {  
    index = (index + 1) % 5; // цикличная последовательность от 0 до 4  
    setState(() {  
      listOfWidget.last = LifeCycleExample(colorIndex: index);  
    });  
  }  
  
  @override  
  Widget build(BuildContext context) {  
    return Column(  
      children: [  
        Row(  
          mainAxisAlignment: MainAxisAlignment.center,  
          children: [  
            TextButton(onPressed: insertItem, child: Text('Добавить')),  
            if (listOfWidget.isNotEmpty) ...[  
              TextButton(onPressed: deleteItem, child: Text('Удалить')),  
              TextButton(onPressed: updateItem, child: Text('Обновить')),  
            ],  
          ],  
        ),  
        ...listOfWidget,  
      ],  
    );  
  }  
}  
  
class LifeCycleExample extends StatefulWidget {  
  final int colorIndex;  
  final colors = [  
    Color(0xFFFFD448),  
    Color(0xFFFD7777),  
    Color(0xFFFF80DE),  
    Color(0xFFFFF4F4),  
    Color(0xFFD1FF48),  
  ];  
  
  LifeCycleExample({this.colorIndex = 0}) {  
    debugPrint('🔵constructor -> ExampleLifeCycle()');  
  }  
  
  @override  
  _LifeCycleExampleState createState() {  
    debugPrint('🔵createState()');  
    return _LifeCycleExampleState();  
  }  
}  
  
class _LifeCycleExampleState extends State<LifeCycleExample> {  
  late String title;  
  
  _LifeCycleExampleState() {  
    debugPrint('🔵constructor -> _ExampleLifeCycleState');  
  }  
  
  void changeTitle() {  
    setState(() {  
      debugPrint('🔵widget#${'{'}identityHashCode(widget){'}'} setState()');  
      title = 'Жизненный цикл + State';  
    });  
  }  
  
  @override  
  void initState() {  
    debugPrint('🔵initState()');  
    title = 'Жизненный цикл';  
    super.initState();  
  }  
  
  @override  
  void didChangeDependencies() {  
    debugPrint('🔵widget#${'{'}identityHashCode(widget){'}'} didChangeDependencies()');  
    super.didChangeDependencies();  
  }  
  
  @override  
  void didUpdateWidget(covariant LifeCycleExample oldWidget) {  
    debugPrint('🔵didUpdateWidget()');  
    debugPrint(''  
        '❌ oldWidget = ${'{'}identityHashCode(oldWidget){'}'}\n'  
        '✅ newWidget = ${'{'}identityHashCode(widget){'}'}');  
    super.didUpdateWidget(oldWidget);  
  }  
  
  @override  
  void deactivate() {  
    debugPrint('🔵widget#${'{'}identityHashCode(widget){'}'} deactivate()');  
    super.deactivate();  
  }  
  
  @override  
  void dispose() {  
    debugPrint('🔵widget#${'{'}identityHashCode(widget){'}'} dispose()');  
    super.dispose();  
  }  
  
  @override  
  Widget build(BuildContext context) {  
    debugPrint('🔵widget#${'{'}identityHashCode(widget){'}'} build()');  
    return GestureDetector(  
      onTap: changeTitle,  
      child: ColoredBox(  
        color: widget.colors[widget.colorIndex],  
        child: Padding(  
          padding: const EdgeInsets.all(8.0),  
          child: Text(title),  
        ),  
      ),  
    );  
  }  
}

Как работает жизненный цикл виджета

Демонстрация жизненного цикла

Добавление виджета

Нажатие "Добавить" вызывает insertItem(). Новый LifeCycleExample добавляется в listOfWidget. Flutter вызывает:


Изменение внутреннего состояния виджета

Нажатие на текст Жизненный цикл вызывает:


Обновление виджета

Нажатие "Обновить" вызывает updateItem(), меняя colorIndex. Flutter вызывает:

Важно: Виджет не пересоздается полностью, а обновляется. Element и State сохраняются (надпись «Жизненный цикл + State» не исчезает).


Удаление виджета

Нажатие "Удалить" вызывает deleteItem(). Последний LifeCycleExample удаляется. Flutter вызывает: