Flutter разбираемся с InheritedWidget

Середина 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. Будем использовать!

Простой пример использования InheritedWIdget

Ссылка на код на github.com

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *