Извлечение данных из формы в диалоговом окне предупреждения в Flutter

#flutter #dart

#flutter #dart

Вопрос:

Для того, чтобы диалоговые окна оповещения моего приложения были согласованными, я создал функцию, которая возвращает диалоговое окно оповещения. Функция немного «рисует», чтобы она выглядела так, как я хочу, но ее базовая структура:

 AlertDialog myAlertDialog({
  @required BuildContext context,
  @required Widget myContent,
  @required Function onAccept,}){

  return AlertDialog(
  content: Column(
      children = [
       myContent,
       FlatButton(child: Text("ok"), onPressed: onAccept),
       FlatButton(child: Text("cancel"), onPressed: Navigator.pop(context),
      ]
    )
  );
}
 

В данном конкретном случае я отправляю форму myContent параметру. В настоящее время я застрял на том, как извлечь данные из формы myContent и передать их функции onAccept .

В случае, если это может быть полезно, ниже приведена базовая структура моей формы:

 class myForm extends StatefulWidget {
  @override
  _myFormState createState() => _myFormState();
}

class _myFormState extends State<myForm> {
@override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: ListView(
        children: [
            TextFormField(onChanged: (val) {setState(() {var1 = val;});}, ... ), //Field 1
            TextFormField(onChanged: (val) {setState(() {var2 = val;});}, ... ), //Field 2
            //...
          ],
        ),
   );
 }
}

 

Спасибо!

РЕДАКТИРОВАТЬ Я заметил, что следующее также может быть полезно / необходимо для того, чтобы моя текущая проблема была более понятной:

Вот как я вызываю AlertDialog из основного виджета.

 class MainWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FlatButton(
        child: Text("launch Alert Dialog",),
        onPressed: () {showDialog(
                        context: context,
                        builder: (BuildContext context) {
                          return _launchAlertDialog(context);
                        },
                      );},
      ),
    );
  }
}

AlertDialog _launchAlertDialog(BuildContext context){
   return myAlertDialog(
     context: context,
     content: myForm(), //Data is captured here
     onAccept: (){}, //But I need to use it here
   );
}
 

Ответ №1:

Если вы прочитаете метод sig для Navigator.pop, вы увидите

 static void pop<T extends Object?>(BuildContext context, [ T? result ]) 
 

Как вы можете видеть из сигнала moths, вы можете вернуть две вещи в pop. контекст и данные результата, которые вы хотите отправить обратно на предыдущий экран. Так что что-то вроде этого будет работать.

 Navigator.pop(context, dataVaribleHere);
 

Более явное определение доступно, если вы используете VSCode и наведите указатель мыши на функцию pop, появится окно подсказки.

Документы из окна подсказки:

void pop(контекст BuildContext, [T результат]) пакет: flutter /src /widgets /navigator.dart

Выделите из навигатора самый верхний маршрут, который наиболее плотно охватывает данный контекст. Метод [Route.didPop] текущего маршрута вызывается первым. Если этот метод возвращает false, то маршрут остается в истории [Navigator] (ожидается, что маршрут выведет какое-то внутреннее состояние; см., Например, [LocalHistoryRoute]). В противном случае применяется остальная часть этого описания.

Если значение не равно нулю, результат будет использоваться как результат выскакивающего маршрута; будущее, которое было возвращено после нажатия на выскакивающий маршрут, завершится результатом. Маршруты, такие как диалоговые окна или всплывающие меню, обычно используют этот механизм для возврата значения, выбранного пользователем, виджету, который создал их маршрут. Тип результата, если он указан, должен соответствовать аргументу типа класса всплывающего маршрута (T).

Всплывающий маршрут и маршрут под ним уведомляются (см. [Route.didPop], [Route.didComplete] и [Route.didPopNext]). Если в [Navigator] есть какие-либо [Navigator.observers], они также будут уведомлены (см. [NavigatorObserver.didPop] ).

Аргумент типа T — это тип возвращаемого значения всплывающего маршрута.

Тип результата, если он указан, должен соответствовать аргументу типа класса всплывающего маршрута (T).

Вам нужно будет перехватить данные на вашем экране, которые вызывают диалоговое окно оповещения. Что-то вроде этого. Обратите внимание, что содержащая функция, которую он показывает в диалоговом окне, является асинхронной функцией. Это важно, так как позволяет нам использовать await ключевое слово, чтобы дождаться завершения диалога перед обработкой.

 _showMyDialog() async {
var result = await showDialog(
    context: context,
    builder: (_) => new AlertDialog(
          title: new Text("Material Dialog"),
          content: new Text("Hey! I'm Coflutter!"),
          actions: <Widget>[
            FlatButton(
              child: Text('Close me!'),
              onPressed: () {
                Navigator.pop(context, dataVaribleHere);
              },
            )
          ],
        ));
          if (result != null) {
            setState(() {
              //Do stuff
            });
          }
  }
 

Вам нужно будет адаптировать этот код к вашей конкретной ситуации, но он показывает основную идею. Обратите внимание, что этот код не будет выполняться как есть. Это научит вас, что это не для копирования / вставки. dataVaribleHere не является реальной переменной, она указывает вам, куда поместить ваши данные.

Комментарии:

1. спасибо, я ценю это. Ваше решение кажется очень полезным для получения данных из AlertDialog . Однако моя текущая проблема заключается в том, как получить данные из формы myForm() и использовать их в функции onAccept . Я просто отредактировал свой вопрос и добавил немного дополнительной информации, чтобы попытаться сделать это более понятным. В частности, пожалуйста, ознакомьтесь с комментариями в коде. Пожалуйста, дайте мне знать, если это имеет смысл.

2. Вы используете ShowDialog точно так же, как в моем примере. Пожалуйста, прочтите это еще раз. Вся необходимая информация содержится в моем ответе. ShowDialog -> новый экран -> возврат к старому экрану с новыми данными.

3. Я завтра посмотрю поближе и дам вам знать. Спасибо, чувак.

4. Я думаю, что вас может смутить то, что ShowDialog принимает виджет. Любой виджет. дайте ему myForm вместо myAlertDialog в конструкторе. Нет необходимости вкладывать его внутрь.

5. Почему вы пытаетесь использовать данные на уровне myAlertDialog? Я ожидаю, что вы хотите использовать его на исходном экране?

Ответ №2:

Мне удалось решить это следующим образом:

(Примечание: возможно, это не самое элегантное решение.. пожалуйста, дайте мне знать, если есть лучший способ)

1) Я создал Map файл, который будет хранить данные.

2) Затем я создал функцию (вызывается saveData ) для добавления данных в Map . Затем я передал эту функцию форме myForm .

3) Это позволяет мне легко обрабатывать данные по адресу onAccept . Эти шаги приведены ниже:

 AlertDialog _launchAlertDialog(BuildContext context){
   Map data = {}; //This will hold my data.
   //And this function edits/adds data to the Map.
   void saveData(String variableName, dynamic value){data[variableName] = value;}
   return myAlertDialog(
     context: context,
     content: myForm(saveData: saveData), //Here, I pass the function to the form.
     onAccept: (){print(data['var1']);}, //Now, I can easily handle the data when the user presses accept.
   );
}
 

4) Наконец, это то, как форма myForm использует saveData для передачи данных в data :

 class myForm extends StatefulWidget {
  final Function saveData;

  myForm({
    @required this.saveData,
  });

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

class _myFormState extends State<myForm> {
@override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: ListView(
        children: [
            TextFormField(onChanged: (val) {setState(() {widget.saveData('var1', val);});}, ... ), //Field 1
            TextFormField(onChanged: (val) {setState(() {widget.saveData('var2', val);});}, ... ), //Field 2
            //...
          ],
        ),
   );
 }
}

 

Ответ №3:

Не радикально отличается от вашего собственного решения, но оно использует внутренний механизм сохранения Form , поэтому это меньше обходного пути. У меня есть следующая вспомогательная функция, чтобы упростить использование диалоговых окон формы из любого места:

 Future<bool> inputFormDialog(BuildContext context, {String? title, required Form Function(BuildContext, GlobalKey) formBuilder, String? okButton, String? cancelButton}) async {
  final formKey = GlobalKey<FormState>();
  final result = await showDialog<bool>(
    context: context,
    barrierDismissible: false,
    builder: (context) => AlertDialog(
      title: (title != null) ? Text(title) : null,
      content: formBuilder(context, formKey),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context, false),
          child: Text(cancelButton?.toUpperCase() ?? MaterialLocalizations.of(context).cancelButtonLabel),
        ),
        TextButton(
          onPressed: () {
            if (formKey.currentState!.validate()) {
              formKey.currentState!.save();
              Navigator.pop(context, true);
            }
          },
          child: Text(okButton?.toUpperCase() ?? MaterialLocalizations.of(context).okButtonLabel),
        ),
      ],
    ),
  );
  return result ?? false;
}
 

Как вы можете видеть, в нем есть некоторые удобства для создания, такие как заголовок, стандартные кнопки со стандартными или предоставленными именами. Небольшое отличие от вашего решения заключается в том, что оно обрабатывает состояние формы автономно и при необходимости вызывает функции проверки и сохранения. Его можно использовать следующим образом:

 if (await inputFormDialog(context, formBuilder: (context, formKey) => buildFormDialog(context, formKey))) {
  setState(() {
    // handle the data set by the form innards, eg. formVariable
  });
}
 

где конструктор форм — это обычная функция конструктора:

 Form buildFormDialog(BuildContext context, GlobalKey formKey) {
  return Form(
    key: formKey,
    child: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        TextFormField(
          keyboardType: TextInputType.text,
          textInputAction: TextInputAction.done,
          decoration: ...,
          validator: ...,
          onSaved: (value) => formVariable = value!,
        ),
        // ...
      ],
    ),
  );
}