Управление стейтом встроенными средствами 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_