Как вызвать метод в дочернем виджете из родительского виджета или вызвать родительский метод из дочернего виджета.

Когда вы читаете заголовок, вы можете подумать: «Чувак, с этим может справиться управление состоянием». Что ж, я не собираюсь этого отрицать. Но в этой статье мы рассмотрим, как выполнять методы родительского и дочернего классов.

Мы рассмотрим 2 части:

  • Как вызвать метод в классе PARENT из дочернего класса
  • Как вызвать метод класса CHILD из класса parent.

Первая тема очень похожа, и я думаю, что большинство разработчиков флаттера часто используют ее. А вот второй — что-то вроде антипатерна.

Зачем нужны отдельные родительский и дочерний виджеты?

Есть много преимуществ, если ваш код хорошо разделен. Для меня разделение виджетов более читабельно и упрощает поддержку кода. Некоторые другие источники также говорят, что мы можем избежать ненужных перестроек виджетов, разделив виджеты на классы. Это верно, но в данном случае это неприменимо.
Это потому, что мы передаем метод в качестве аргумента дочернему классу. Это сделает его непостоянным объектом. Таким образом, когда родительский виджет перестраивается, дочерний виджет также перестраивается.

Как вызвать метод класса PARENT из дочернего класса

Родительский метод легко выполнить из дочернего класса. Что нам нужно сделать, так это использовать .call() в методе, который мы собираемся выполнить.

// example call void method
methodName?.call(); // if its nullable variable, use ? sign 

// example if method with argument
methodName?.call(args); 

eg:

Мы хотим вызвать значение обновления String в родительском классе с помощью метода methodFromParent в дочернем виджете.

child.dart

class Child extends StatelessWidget {
  const Child({super.key, this.methodFromParent});
  final Function(String val)? methodFromParent;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        methodFromParent?.call("Updated from child");
      },
      child: const Text("Update parent"),
    );
  }
}

parent.dart

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  String parentTxt = "Initial Text";
  int count = 0;
  void updateParentTxt(String param) {
    count++;
    parentTxt = '$param $count times';
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(parentTxt)),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          /// another child widget
          Child(methodFromParent: updateParentTxt),
        ],
      ), // pass the method as argument
    );
  }
}

Полный код и демонстрация: DartPad

В этом примере мы видим, что updateParentTxt находятся в родительском классе.

void updateParentTxt(String param) {
    count++;
    parentTxt = '$param $count times';
    setState(() {});
  }

Мы выполняем его, нажимая кнопку ElevatedButton в дочернем классе. В начальном представлении текст на панели приложений имеет вид initial Text, затем после нажатия дочерней кнопки он меняется на время нажатия кнопки.

Если у вас в голове много вопросов, пожалуйста, сохраните ее и прочитайте вторую тему. Мы будем использовать сложные примеры в конце этой статьи.

😄😄😄

Как вызвать метод класса CHILD из класса parent.

У нас есть 2 варианта вызова метода из родительского класса. Первый — с помощью GlobalKey, а второй — с помощью custombuilder.

Мне нравится использовать builder, и мы рассмотрим его в этой статье. Но если вас интересует GlobalKey, вы можете найти его в этом вопросе [ссылка]

шаги:

  • определить typedef для пользовательского компоновщика
typedef MyBuilder = void Function(BuildContext context, void Function() methodFromChild);
  • используйте конструктор как arguments в конструкторе Child.
  • Так как нам нужно context для билдера, мы можем вызвать билдер внутри метода Widget build()

child.dart

class Child extends StatefulWidget {
  final MyBuilder builder;

  const Child ({Key? key, required this.builder}) : super(key: key);

  @override
  _ChildState createState() => _ChildState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    widget.builder.call(context, childMethod); // <<<<<   call it here
    return Text('Child widget');
  }

  void childMethod() {
    print('test');
  }
}

следующий…

  • Инициализируйте метод в родительском классе: `late void Function() myMethod; и используйте конструктор для виджета Child.
  • myMethod выполнит метод в дочернем виджете.

parent.dart

class ParentWidget extends StatelessWidget {
  late void Function() myMethod;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body:Column(
            children: [
              IconButton(
                icon: Icon(Icons.help),
                onPressed: () {
                  myMethod.call(); // << this will execute the methodFromChild
                },
              ),
              Child(
                builder:
                    (BuildContext context, void Function() methodFromChild) {
                  myMethod = methodFromChild;
                },
              ) ],
          ),
        ),
    );
  }
}

Полный код и демонстрация: DartPad

из приведенного выше примера, когда мы нажимаем кнопку IconButton, она выполняет функцию myMethod. Это означает, что мы уже назначаем строитель myMethod = methodFromChild;

В дочернем классе мы используем childMethod для аргумента построителя.

void childMethod() {
    print('test');
  }

теперь в родительском классе каждый раз, когда мы вызываем, нажмите IconButton и выполните myMethod, он выполнит childMethod

Честно говоря, мне это кажется потрясающим. 😃

Мы можем вызвать метод с обеих сторон. Родительский класс и дочерний класс. Хорошо, теперь давайте посмотрим, как это реализовано.

Вот один пример обработки представления на рабочем столе. Размер экрана шире мобильного, помещается по горизонтали.

Это родительский код выглядит так:

class ParentWidget extends StatefulWidget {
....

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text("Responsive View")),
    body: Row(
      children: [
        Expanded( child: // some widgets in the parents,
        Expanded( child: Child1()),
        Expanded( child: Child2()),
.....

Полный код и демонстрация: DartPad

Демонстрационный результат:

Parent , Child1 и Child2 являются отдельными классами. Невозможно обновить значение из разных классов.

— Case on Child2:
в этом случае действие пользователя по нажатию кнопки. После показа диалогового окна и заполнения формы оно АВТОМАТИЧЕСКИ обновит локальное значение child2Txt, а также обновит переменную в родительском классе.

После закрытия диалога мы видим

  • в дочернем виджете 2: Text from dialog : LOREM ipsum
  • в родительском виджете: Text from Child 2: LOREM ipsum

в этом случае мы реализуем: Вызовите метод в родительском классе из дочернего класса, используя: widget.methodFromParent?.call(dialgTxt);

— Случай с Child1:
в этом виджете у нас есть TextField. Этот виджет имеет свой собственный контроллер состояния. Как видите, я могу набирать все виджеты TextField.

Как мы можем обновить значение родительского класса?

Здесь 2 варианта:

  • Вызов метода в родительском классе внутриonChaged свойства.
TextField(
  onChanged: (val) {
   widget.methodFromParent?.call(val);
  },
)

Конечно, это обновит значение в родительском классе. Но, как мы знаем, onChanged прослушивает каждое изменение в TextField. Можете себе представить, когда я наберу Lorem, метод выполнится 5 раз.

L, Lo, Lor, Lore и последний Lorem

Я думаю, что это совсем плохо, так как в родительском методе мы также вызываем setState(). только тип Lorem, нам нужно 5 раз пересобрать виджет.

В Child2 мы вызываем родительский метод только тогда, когда диалоговое окно закрыто.

  • Вызов метода в дочернем классе из родительского класса

Сначала создайте метод для сбора всех значений в классе Child1:

void _localMethod() {
  print("invoke method in Child 1");
  final collectedString = [_ctlr.text, _ctlr2.text, _ctlr3.text];
  widget.methodFromParent?.call(collectedString);
}

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

Мы можем собрать все данные из дочернего класса и сохранить их в базу данных. Нет необходимости прослушивать значение onChanged из виджета TextField.

Спасибо за конец. Хлопайте 👏 и делитесь, если вам понравилась эта статья. Не стесняйтесь оставлять любые комментарии. Я хотел бы обсудить больше.

Пометьте код на Github, чтобы сохранить его на потом: call_method_parent_child.dart (github.com)