README.md

Управление стейтом встроенными средствами Flutter

Ветка a_setstate_01 StatefulWidget и setState()

  • используем ссылку на доменную модель
  • при изменении модели вызываем метод setState()

Ветка a_setstate_02 StatefulWidget и ChangeNotifier

  • создаем класс наследующий ChangeNotifier
class HomeChangeNotifier extends ChangeNotifier
  • создаем метод для вызова слушателей
  // вызываем при изменении значений свойств
  void _updateNotify(VoidCallback callback) {
    callback();
    // оповещение слушателей об изменении состояния
    notifyListeners();
  }
  • изменение модели осуществляем в этом методе
  // Метод изменяющий состояние
  void onChange({required DateTime birthdate}) => _updateNotify(() {...});
  • в UI используем StatefulWidget

    • создаем ссылку на notifier

      // ссылка на состояние
      final _notifier = HomeChangeNotifier();
      
    • создаем метод-ссылку

      // метод-слушатель для notifier -- вызовает метод обновления
      void _update() => setState(() {});
      
    • подписываем и отписываем слушателя

      @override
      void initState() {
      super.initState();
      // добавляем слушателя
      _notifier.addListener(_update);
      }
      
      @override
      void dispose() {
      // удаляем слушателя
      _notifier.removeListener(_update);
      super.dispose();
      }
      
    • для обновления модели

      // вызываем изменение стейта
      _notifier.onChange(birthdate: date);
      

Ветка a_setstate_03 StatefulWidget и ValueNotifier

  • создаем класс наследующий ValueNotifier и типизированный типом модели
class AgeValueNotifier extends ValueNotifier<AgeFormatter> {
  AgeValueNotifier({required AgeFormatter value}) : super(value);

  // изменяющий ссылку на модель и оповещающий об изменении
  void onChanged({required DateTime birthdate}) {
    // измением модель
    super.value =
        AgeFormatter(age: Age(birthdate: birthdate, today: DateTime.now()));
    // оповещаем слушателей
    notifyListeners();
  }
}

Следует обратить внимание на то, что в этом классе мы создаем только метод для обработки события из UI.

  • в UI используем StatefulWidget

    • для получения доступа к значениям свойств модели осуществляется через value

      Text('Ваша дата рождения: ${_notifier.value.birthdate}'),
      
    • вызов метода измения состояния

      // вызываем изменение метод у оповещателя
      _notifier.onChanged(birthdate: date);
      
    • остальная организация кода аналогична работе с ChangeNotifier

Ветка a_inherited_widget_01 InheritedWidget, StatelessWidget, StatefulBuilder

  • для хранения ссылки на модель и ее свойства создаем класс наследующий InheritedWidget
class AgeInheritedWidget extends InheritedWidget {
  AgeInheritedWidget({Key? key, required Widget child})
      : super(key: key, child: child) {
    _ageContainer = AgeContainer();
  }

  // ссылка на модель
  late final AgeContainer _ageContainer;

  // СВОЙСТВА для отображения на UI
  String get age => _ageContainer.age;
  String get birthdate => _ageContainer.birthdate;
  String get days => _ageContainer.days;

  // изменяющий модель
  void onChanged({required DateTime birthdate}) {
    _ageContainer.changeAge(birthdate);
  }

  // реализация, которая не подписывает на изменения
  // в этом InheritedWidget
  static AgeInheritedWidget of(BuildContext context){
    Widget? result = context
        .getElementForInheritedWidgetOfExactType<AgeInheritedWidget>()
        ?.widget;
    assert(result != null, 'Not found AgeInheritedWidget widget');
    return result! as AgeInheritedWidget;
  }
  // не собираемся изменять UI поэтому метод должен вернуть false
  @override
  bool updateShouldNotify(covariant AgeInheritedWidget oldWidget)=> false;
}
  • в UI выше по дереву виджетов, создаем экземпляр этого класса
  • на странице используем StatefulBuilder
  • через метод of() получаем ссылку на экземпляр нашего InheritedWidget
body: AgeInheritedWidget(
        child: Center(
          // используем вместо StatefulWidget StatefulBuilder
          child: StatefulBuilder(builder: (context, setState) {
            // получаем ссылку на данные в InheritedWidget
            final inheritWidget = AgeInheritedWidget.of(context);
            ...
  • в обработчике нажатия на кнопку используем setState() в котором вызываем изменение модели
// вызываем изменение стейта
  setState(() {
    // вызываем изменение даты
    inheritWidget.onChanged(birthdate: date);
  });
  • фактически класс AgeInheritedWidget используется как хранилище ссылки на модель и ее свойства.
  • в случае вызова setState() происходит пересборка ниже лежащих виджетов с перечиткой значений из AgeInheritedWidget.

Ветка a_inherited_widget_02 InheritedWidget, StatefulWidget

В отличие от a_inherited_widget_01 используется StatefulWidget вместо StatefulBuilder.

В классе стейта для получения ссылки на унаследованный виджет используется метод didChangeDependencies.

late AgeInheritedWidget inheritWidget;

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  // получаем ссылку на данные в InheritedWidget
  inheritWidget = AgeInheritedWidget.of(context);
}

Ветка a_inherited_widget_03 обновляемый InheritedWidget внутри StatefulWidget

  • создаем StatefulWidget

    • в конструкторе виджета определяем ссылку на дочерний виджет
    • в виджет создаем of методы для получения ссылок данные и методы модели

      //-- стейтфул виджет, стейт которого служит оберткой для InheritedWidget
      class AgeProvider extends StatefulWidget {
      const AgeProvider({
      Key? key,
      required this.child,
      }) : super(key: key);
      
      // ссылка на дочерний
      final Widget child;
      
      @override
      State<AgeProvider> createState() => _AgeProviderState();
      
      // получение ссылки на динамичные данные модели для отображения
      static AgeContainer valueOf(BuildContext context) {
      _AgeProvider? inheritedWidget =
           context.dependOnInheritedWidgetOfExactType<_AgeProvider>();
      return inheritedWidget!.data.model;
      }
      
      // получение ссылки на метод изменяющий данные модели
      static InputDate functionOf(BuildContext context) {
      var inheritedElement =
           context.getElementForInheritedWidgetOfExactType<_AgeProvider>();
      InputDate function =
           (inheritedElement?.widget as _AgeProvider).data.onChanged;
      return function;
      }
      }
      
    • в стейте определяем ссылку на модель, метод изменяющий модель с setState
    • в методе build возвращаем экземпляр InheritedWidget

      //-- стейт, который содержит ссылку на модель и метод изменения модели
      //-- а также служит оберткой для InheritedWidget
      class _AgeProviderState extends State<AgeProvider> {
      // ссылка на модель
      final AgeContainer model = AgeContainer();
      
      // изменяющий модель
      void onChanged({required DateTime birthdate}) {
      // вызываем обновление в том числе для InheritedWidget
      setState(() {
        model.changeAge(birthdate);
      });
      }
      
      @override
      Widget build(BuildContext context) {
      // здесь только возвращаем InheritedWidget
      // со ссылкой на себя(стейт)
      // и дочерний виджет полученный через конструктор виджета
      return _AgeProvider(
        data: this,
        child: widget.child,
      );
      }
      }
      
  • создаем InheritedWidget со ссылкой на стейт

    class _AgeProvider extends InheritedWidget {
      const _AgeProvider({
      Key? key,
      required this.data,
      required Widget child,
      }) : super(
              key: key,
              child: child,
            );
    
      // храним ссылку на стейт
      final _AgeProviderState data;
    
      // всегда обновляемся при построении
      @override
      bool updateShouldNotify(covariant InheritedWidget oldWidget) => true;
    }
    
  • на странице задействуем в работу так

    @override
    Widget build(BuildContext context) {
      return Scaffold(
      // создаем экземпляр StatefulWidget,
      // который оборачивает InheritedWidget
      body: AgeProvider(
          child: Center(
            child: AgeInputOutputWidget(),
          ),
      ),
      );
    }
    
  • отображаем данные модели так

    @override
    Widget build(BuildContext context) {
      // получаем ссылку на данные модели
      // через InheritedWidget -> _AgeProviderState -> AgeContainer
      var ageContainer = AgeProvider.valueOf(context);
      print('OutputWidget builder');
      return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
          Text('Ваша дата рождения: ${ageContainer.birthdate}'),
          SizedBox(
            height: 10,
          ),
          Text('Ваш возраст: ${ageContainer.age}'),
          SizedBox(
            height: 10,
          ),
          Text('Текущий проживаемый день: ${ageContainer.days}'),
      ],
      );
    }
    
  • обновляем данные модели так

    @override
    Widget build(BuildContext context) {
      // получаем ссылку на метод через InheritedWidget в _AgeProviderState
      final InputDate func = AgeProvider.functionOf(context);
      print('InputWidget builder');
      return ElevatedButton(
      onPressed: () async {
          DateTime? date = await showDatePicker(
            context: context,
            initialDate: DateTime.now(),
            firstDate: DateTime(1965),
            lastDate: DateTime(2023),
          );
          if (date == null) {
            return;
          }
          // вызываем метод из _AgeProviderState
          func(birthdate: date);
      },
      child: Text('Выбрать'),
      );
    }
    

    Ветка a_

Описание

Работа с со стейтом во флатер без фреймворков

Конвейеры
0 успешных
0 с ошибкой