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 с ошибкой