Середина 2021. Осваиваю по чуть-чуть фреймворк Flutter от угла (ой, от гугла 🙂 В итоге рождается такое приложение «Игра-викторина по истории России«. А так как это «пет-проект», то я издеваюсь над ним, как хочу и пытаюсь применить всякие интересные паттерны, механики и забавные виджеты. Я уже сделал изменение виджетов по методологии BLOC. Теперь на части проекта решил разобраться с таким виджетом, как InheritedWidget. Понять его возможности, ограничения, проникнуться им. Найденные примеры показались мне не таким уж показательными (простите за тавтологию). И немного разобравшись, в итоге, я решил изложить свой пример. Во первых, для себя, чтобы потом было проще вспомнить, во-вторых, вдруг еще кому пригодится… Материал освоен из нескольких статей в интернете, но основная, самая полезная, мне показалось, вот эта. Мой пример, надеюсь, получился чуть проще, чем из той статьи, но посложнее, чем многие другие. Оптимальный…
Есть только один важный нюанс. InheritedWidget работает только в рамках одного дерева-экрана. При переходе navigator.push или типа того, контекст теряется… Так что, его применение, как я понимаю, только для оптимизации скорости отрисовки изменений на сложных экранах, когда виджеты можно делать Stateless и менять точечно.
Или оборачивать в виджет MaterialApp, к примеру… А еще нужно учитывать, что для работы этого механизма нужен обязательно контекст и дергать методы лучше по кнопке, после отрисовки экрана.
Делаем свой класс от InheritedWIdget, который будет отслеживать изменения в нашем виджете-обертке:
class MyInheritedWidget extends InheritedWidget {
final MyMoneyWidgetState data;
MyInheritedWidget({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
@override
class MyInheritedWidget extends InheritedWidget {
final MyMoneyWidgetState data;
MyInheritedWidget({
required Widget child,
required this.data,
}) : super(child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
Так же делаем «обертку» для этого виджета, в котором будут происходить изменения:
class MyMoneyWidget extends StatefulWidget {
final Widget child;
const MyMoneyWidget({required this.child}) : super();
static MyMoneyWidgetState of(BuildContext context, {bool rebuild = true}) {
//если нам нужно только получать данные и не перестраивать конкретный виджет, то ставим rebuild = false
if (rebuild) {
return context
.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()!
.data;
}
return context.findAncestorWidgetOfExactType<MyInheritedWidget>()!.data;
}
@override
State<StatefulWidget> createState() {
return MyMoneyWidgetState();
}
}
class MyMoneyWidgetState extends State<MyMoneyWidget> {
int _myMoney = 0;
int get myMoney => _myMoney;
void setMyMoney(newMyMoney) {
setState(() {
_myMoney = newMyMoney;
});
}
@override
Widget build(BuildContext context) {
return MyInheritedWidget(
child: widget.child,
data: this,
);
}
}
В этой обертке мы объявляем переменную (данные), которую будем получать в нужно месте и устанавливать в другом. Пусть это будут условные монеты 🙂 В параметр data передаем State самого этого виджета через this, и в качестве child своего «ребенка».
Где-то на верхнем уровне обертываем виджет, которому подчинены нужные нам «дети» (и тот, где отображаются данные, и тот, где будем менять данные), в нашу обертку MyMoneyWIdget. Для примера сделаем на самой первой странице два подвиджета. Один выводит данные, второй изменяет.
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Пример с InheritedWidget'),
),
body: Center(
child: MyMoneyWidget(
child: Center(
child: Column(
children: [
MoneyWidget(),
ButtonWidget(), //кнопка перестраивается
ButtonWidgetNotChaned(), //кнопка не перестраивается
],
),
),
)),
);
}
}
Виджет, который принимает и выводит нужную информацию:
class MoneyWidget extends StatelessWidget{
@override
Widget build(BuildContext context) {
MyMoneyWidgetState data = MyMoneyWidget.of(context);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Text('У меня: ${data.myMoney} монет'),
],
),
);
}
В параметр data получаем контекст нашего «главного» виджета и оперируем его переменными и функциями. В данном случае, просто «слушаем» переменную через get myMoney.
Второй виджет, кнопка, который «дергает» функцию нашего «главного» виджета и тот устанавливает нужные нам данные во всех подчиненных виджетах, которые его «слушают».
class ButtonWidget extends StatelessWidget{
@override
Widget build(BuildContext context) {
MyMoneyWidgetState data = MyMoneyWidget.of(context);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
GestureDetector(
onTap: () {
data.setMyMoney(data.myMoney + 100);
},
child: Text('Добавить к ' + data.myMoney.toString()),
)
],
),
);
}
}
Здесь мы и слушаем данные и меняем их.
И третий виджет, при нажатии на который значение увеличивается, а сам этот виджет не обновляется, за счет параметра rebuild, устанавливаемого в false. Может потребоваться как раз для кнопок, чтобы они лишний раз не перерисовывались, если на них нет обновляемой информации.
class ButtonWidgetNotChaned extends StatelessWidget {
@override
Widget build(BuildContext context) {
MyMoneyWidgetState data = MyMoneyWidget.of(context, rebuild: false);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
GestureDetector(
onTap: () {
data.setMyMoney(data.myMoney + 200);
},
child: Text('Прибавить к ' + data.myMoney.toString()),
)
],
),
);
}
}
В итоге получаем, что нажимая на текст «Добавить к ХХХ», мы изменяем наши монетки в тексте, во втором виджете. А нажимая «Прибавить к 0» мы прибавляем, но сама эта «кнопка» не меняется. В принципе, кода писать и встраиваться в дерево виджетов сравнимо с BLOC. Будем использовать!
Ссылка на код на github.com