如何将“material_tag_editor”中用户提交的标签数据添加到“Flutter Form Builder”表单中?

Posted

技术标签:

【中文标题】如何将“material_tag_editor”中用户提交的标签数据添加到“Flutter Form Builder”表单中?【英文标题】:How to add user-submitted tags data from "material_tag_editor" into a "Flutter Form Builder" form? 【发布时间】:2021-04-06 07:59:42 【问题描述】:

我正在使用"Flutter Form Builder" package 4.0.2 构建一个表单并尝试添加两个字段,用户通过"material_tag_editor" package 0.0.6 输入“标签”

问题:当通过按下“发布”按钮提交表单时,不包括为这些“标签”表单字段(Q1 或 Q3)提交的数据(参见下面的控制台屏幕截图)。

注意“flutter: qFour: 30, qFive: sample answer to q5, qTen: sample answer to q10”这一行 - Q1 和 Q3 都不包括在内(我在单独的打印语句中添加了它们的数据,所以你会看到它们在控制台中 - 查找 >>> 行)。

这里是输入了示例标签的表单截图(iPhone 模拟器截图),以及带有按钮的表单底部:

代码如下:

import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:streakers_journal_beta/screens/reviews_screen.dart';
import 'package:streakers_journal_beta/screens/welcome_screen.dart';
import 'package:streakers_journal_beta/models/user.dart';
import 'package:rflutter_alert/rflutter_alert.dart';

import 'package:flutter_form_builder/flutter_form_builder.dart';

// BEGIN code from material_tag_editor
import 'package:material_tag_editor/tag_editor.dart';
import 'package:material_tag_editor/tag_editor_layout_delegate.dart';
import 'package:material_tag_editor/tag_layout.dart';
import 'package:material_tag_editor/tag_render_layout_box.dart';
// END code from material_tag_editor

//import 'dart:html';
//import 'dart:convert';

// This is the stateful widget that the main application instantiates, per https://api.flutter.dev/flutter/widgets/Form-class.html
class SandboxWriteReviewScreen extends StatefulWidget 
  // BEGIN code from material_tag_editor
  final String title = 'Material Tag Editor Demo';
// END code from material_tag_editor

  @override
  _SandboxWriteReviewScreenState createState() =>
      _SandboxWriteReviewScreenState();


// This is the private State class that goes with WriteReviewScreen
class _SandboxWriteReviewScreenState extends State<SandboxWriteReviewScreen> 
  var data;
  AutovalidateMode autovalidateMode = AutovalidateMode.always;
  bool readOnly = false;
  bool showSegmentedControl = true;
  //final _newFormbuilderKey = GlobalKey<FormState>();
  final _newnewFormbuilderKey = GlobalKey<FormBuilderState>();

  // above "GlobalKey" lets us generate a unique, app-wide ID that we can associate with our form, per https://fluttercrashcourse.com/blog/realistic-forms-part1
  final ValueChanged _onChanged = (val) => print(val);

  // BEGIN  related to FormBuilderTextField in form below
  final _ageController = TextEditingController(text: '45');
  bool _ageHasError = false;
  // END related to FormBuilderTextField in form below

  String qEleven;
  String qTwelve;

  // BEGIN code from material_tag_editor
  List<String> qOne = [];
  final FocusNode _focusNode = FocusNode();

  onDelete(index) 
    setState(() 
      qOne.removeAt(index);
    );
  

  // below = reiteration for cons

  List<String> qThree = [];
  //final FocusNode _focusNode = FocusNode();

  uponDelete(index) 
    // NOTE: "uponDelete" for cons vs. "onDelete" for pros
    setState(() 
      qThree.removeAt(index);
    );
  

// END code from material_tag_editor

  //final _user = User();

  List<bool> isSelected;

  int starIconColor =
      0xffFFB900; // was 0xffFFB900;  0xffD49428 is from this image: https://images.liveauctioneers.com/houses/logos/lg/bartonsauction550_large.jpg?auto=webp&format=pjpg&width=140

  @override
  void initState() 
    //isSelected = [true, false];
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        actions: [
          Padding(
            padding: EdgeInsets.only(right: 12.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  icon: Icon(Icons.keyboard_backspace),
                  onPressed: () 
                    Navigator.pop(context);
                  ,
                ),
                Text(
                  'back',
                  style: TextStyle(
                    fontSize: 7,
                  ),
                ),
              ],
            ),
          ),
        ],

        leading: Icon(
          Icons.rate_review,
          color: Colors.black54,
        ),
        title: Column(
          children: [
            Text(
              'SANDBOX Write a Review',
              style: TextStyle(
                color: Colors.white,
                fontSize: 16,
              ),
            ),
            SizedBox(
              height: 6.0,
            ),
            Text(
              'flutter_form_builder ^4.0.2',
              style: TextStyle(
                color: Colors.limeAccent,
                fontSize: 14,
              ),
            ),
            SizedBox(
              height: 6.0,
            ),
          ],
        ),
        // BEGIN appBar gradient code, per https://medium.com/flutter-community/how-to-improve-your-flutter-application-with-gradient-designs-63180ba96124
        flexibleSpace: Container(
          decoration: BoxDecoration(
            color: Colors.indigoAccent,
          ),
        ),
        backgroundColor: Colors.white,
        centerTitle: false,
      ),
      body: SingleChildScrollView(
        child: Container(
          child: Builder(
            builder: (context) => FormBuilder(
              // was "builder: (context) => Form("
              key: _newnewFormbuilderKey,
              initialValue: 
                'date': DateTime.now(),
              ,
              child: Padding(
                padding: const EdgeInsets.all(14.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    SizedBox(
                      height: 12.0,
                    ),
                    RichText(
                      text: TextSpan(
                        style: TextStyle(
                          color: Colors.blue,
                        ),
                        children: <TextSpan>[
                          TextSpan(
                            text:
                                'Q1 via TagEditor', // was 'What are 3 good or positive things about the house, property or neighborhood?', //  [ 1 ​]
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 16.0,
                            ),
                          ),
                          TextSpan(
                            text: '  (optional)',
                            style: TextStyle(
                              fontWeight: FontWeight.normal,
                              fontStyle: FontStyle.italic,
                              fontSize: 14.0,
                              color: Colors.black54,
                            ), // was 'misleading or inaccurate?',
                          ),
                        ],
                      ),
                    ),
                    // BEGIN code from material_tag_editor
                    Padding(
                      padding: const EdgeInsets.only(top: 16.0),
                      child: TagEditor(
                        length: qOne.length,
                        delimiters: [
                          ','
                        ], // was delimiters: [',', ' '],  Also tried "return" ('\u2386',) and '\u2386'
                        hasAddButton: true,
                        textInputAction: TextInputAction
                            .next, // moves user from one field to the next!!!!
                        autofocus: false,
                        maxLines: 1,

                        // focusedBorder: OutlineInputBorder(
                        //   borderSide: BorderSide(color: Colors.lightBlue),
                        //   borderRadius: BorderRadius.circular(20.0),
                        // ),
                        inputDecoration: const InputDecoration(
                          // below was "border: InputBorder.none,"
                          isDense: true,
                          border: OutlineInputBorder(
                            borderRadius: const BorderRadius.all(
                              const Radius.circular(20.0),
                            ),
                          ),
                          focusedBorder: OutlineInputBorder(
                            borderSide: BorderSide(color: Colors.lightBlue),
                            borderRadius: const BorderRadius.all(
                              const Radius.circular(20.0),
                            ),
                            // above is per https://github.com/flutter/flutter/issues/5191
                          ),
                          labelText: 'separate,  with,  commas',
                          labelStyle: TextStyle(
                            fontStyle: FontStyle.italic,
                            backgroundColor:
                                Color(0x65dffd02), // was Color(0xffDDFDFC),
                            color: Colors.black87, // was Color(0xffD82E6D),
                            fontSize: 14,
                          ),
                        ),
                        onTagChanged: (value) 
                          setState(() 
                            qOne.add(value);
                          );
                        ,
                        tagBuilder: (context, index) => _Chip(
                          index: index,
                          label: qOne[index],
                          onDeleted: onDelete,
                        ),
                      ),
                    ),
                    // END code from material_tag_editor
                    SuperDivider(),
                    // END Chips Input
                    RichText(
                      text: TextSpan(
                        style: TextStyle(
                          color: Colors.blue,
                        ),
                        children: <TextSpan>[
                          TextSpan(
                            text:
                                '​​Q3 via TagEditor (skipped Q2, for simplicity)', //  [ 2 ​]  was '​​List up to 3 negatives, or things you don’t like, about the house, property or neighborhood:',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 16.0,
                            ),
                          ),
                          TextSpan(
                            text: '(optional)', //  was text: '\n(optional)',
                            style: TextStyle(
                              fontWeight: FontWeight.normal,
                              fontStyle: FontStyle.italic,
                              fontSize: 14.0,
                              backgroundColor:
                                  Color(0x70DDFDFC), // was Color(0x30F8A0A2),
                              color: Colors.black54, // was Color(0xffD82E6D),
                              //color: Colors.black54,
                            ), // was 'misleading or inaccurate?',
                          ),
                        ],
                      ),
                    ),
                    // BEGIN code from material_tag_editor
                    Padding(
                      padding: const EdgeInsets.only(top: 16.0),
                      child: TagEditor(
                        length: qThree.length,
                        delimiters: [','], // was delimiters: [',', ' '],
                        hasAddButton: true,
                        textInputAction: TextInputAction
                            .next, // moves user from one field to the next!!!!
                        autofocus: false,
                        maxLines: 1,
                        // focusedBorder: OutlineInputBorder(
                        //   borderSide: BorderSide(color: Colors.lightBlue),
                        //   borderRadius: BorderRadius.circular(20.0),
                        // ),
                        inputDecoration: const InputDecoration(
                          // below was "border: InputBorder.none,"
                          isDense: true,
                          border: OutlineInputBorder(
                            borderRadius: const BorderRadius.all(
                              const Radius.circular(20.0),
                            ),
                          ),
                          focusedBorder: OutlineInputBorder(
                            borderSide: BorderSide(color: Colors.lightBlue),
                            borderRadius: const BorderRadius.all(
                              const Radius.circular(20.0),
                            ),
                            // above is per https://github.com/flutter/flutter/issues/5191
                          ),
                          labelText: 'separate,  with,  commas',
                          labelStyle: TextStyle(
                            fontStyle: FontStyle.italic,
                            backgroundColor:
                                Color(0x65dffd02), // was Color(0xffDDFDFC),
                            color: Colors.black87, // was Color(0xffD82E6D),
                            fontSize: 14,
                          ),
                        ),
                        onTagChanged: (value) 
                          setState(() 
                            qThree.add(value);
                          );
                        ,
                        tagBuilder: (context, index) => _Chip(
                          index: index,
                          label: qThree[index],
                          onDeleted: uponDelete,
                        ),
                      ),
                    ),
                    // END code from material_tag_editor
                    SuperDivider(),
                    RichText(
                      text: TextSpan(
                        style: TextStyle(
                          color: Colors.blue,
                        ),
                        children: <TextSpan>[
                          TextSpan(
                            text:
                                '​​Q4 - via FormBuilder\'s FormBuilderRadioGroup', //  [ 3 ​]
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 16.0,
                            ),
                          ),
                          TextSpan(
                            text: '  (required)',
                            style: TextStyle(
                              fontWeight: FontWeight.normal,
                              fontStyle: FontStyle.italic,
                              fontSize: 14.0,
                              color: Colors.red[700],
                            ), // was 'misleading or inaccurate?',
                          ),
                        ],
                      ),
                    ),
                    FormBuilderRadioGroup(
                      name: 'qFour',
                      decoration: const InputDecoration(
                        border: InputBorder.none,
                        labelStyle: TextStyle(fontStyle: FontStyle.italic),
                      ),
                      wrapVerticalDirection: VerticalDirection.down,
                      // orientation: GroupedRadioOrientation.vertical,
                      orientation: OptionsOrientation.vertical,
                      onChanged: _onChanged,
                      options: [
                        FormBuilderFieldOption(
                            value: '0', child: Text('Never')),
                        FormBuilderFieldOption(
                            value: '30', child: Text('Within the last month')),
                        FormBuilderFieldOption(
                            value: '180',
                            child: Text('Within the last 6 months')),
                        FormBuilderFieldOption(
                            value: '181',
                            child: Text('More than 6 months ago')),
                      ],
                    ),
                    SuperDivider(),
                    Center(
                      child: RichText(
                        text: TextSpan(
                          style: TextStyle(
                            color: Colors.blue,
                          ),
                          children: <TextSpan>[
                            TextSpan(
                              text:
                                  'Q5 - via FormBuilder\'s FormBuilderTextField', //  [ 4 ​]
                              style: TextStyle(
                                fontWeight: FontWeight.bold,
                                fontSize: 16.0,
                              ),
                            ),
                            TextSpan(
                              text: '  (optional)',
                              style: TextStyle(
                                fontWeight: FontWeight.normal,
                                fontStyle: FontStyle.italic,
                                fontSize: 14.0,
                                color: Colors.black54,
                              ), // was 'misleading or inaccurate?',
                            ),
                          ],
                        ),
                      ),
                    ),
                    GavTextField(
                      maxCharLength: 200,
                      fieldAttribute: 'qFive',
                      fieldLabelText: '',
                    ),
                    SuperDivider(),
                    RichText(
                      text: TextSpan(
                        style: TextStyle(
                          color: Colors.blue,
                        ),
                        children: <TextSpan>[
                          TextSpan(
                            text:
                                'Q10 - via FormBuilder\'s FormBuilderTextField  (skipped Q6 - Q9, for simplicity)', // [ 9 ​]
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 16.0,
                            ),
                          ),
                          TextSpan(
                            text: '  (optional)',
                            style: TextStyle(
                              fontWeight: FontWeight.normal,
                              fontStyle: FontStyle.italic,
                              fontSize: 14.0,
                              color: Colors.black54,
                            ), // was 'misleading or inaccurate?',
                          ),
                        ],
                      ),
                    ),
                    GavTextField(
                      maxCharLength: 1200,
                      fieldAttribute: 'qTen',
                      fieldLabelText:
                          'Be honest & kind.', // was 'Be honest, but kind.',
                    ),
                    SuperDivider(),
                    Padding(
                      padding: const EdgeInsets.symmetric(vertical: 16.0),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          ElevatedButton(
                            style: ElevatedButton.styleFrom(
                                primary: Colors.purple,
                                padding: EdgeInsets.symmetric(
                                    horizontal: 50, vertical: 20),
                                textStyle: TextStyle(
                                    fontSize: 20, fontWeight: FontWeight.bold)),
                            onPressed: () 
                              _newnewFormbuilderKey.currentState.save();
                              if (_newnewFormbuilderKey.currentState
                                  .validate()) 
                                print(_newnewFormbuilderKey.currentState.value);
                                print(
                                  '  >>> Q1\'s value via separate print: $qOne',
                                );
                                print(
                                  '  >>> Q3\'s value via separate print: $qThree',
                                );
                               else 
                                print("validation failed");
                              
                            ,
                            child: Text(
                              'Post',
                              style: TextStyle(
                                color: Colors.white,
                                fontSize: 20,
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    SizedBox(
                      height: 200.0,
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  


class GavTextField extends StatelessWidget 
  GavTextField(
      @required this.maxCharLength,
      @required this.fieldAttribute,
      @required this.fieldLabelText);

  int maxCharLength;
  String fieldAttribute;
  String fieldLabelText;

  @override
  Widget build(BuildContext context) 
    return Padding(
      padding: const EdgeInsets.only(top: 16.0),
      child: FormBuilderTextField(
        name: '$fieldAttribute',
        // BEGIN countdown to max number of characters, per https://***.com/a/64035861/1459653
        maxLength: maxCharLength,
        maxLines: null,
        buildCounter: (
          BuildContext context, 
          int currentLength,
          int maxLength,
          bool isFocused,
        ) 
          return Text(
            '$maxLength - currentLength',
          );
        ,
        // END countdown to max number of characters, per https://***.com/a/64035861/1459653
        decoration: InputDecoration(
          labelText:
              '$fieldLabelText', // was "  Separate items,  with,  commas",
          //counterText: _textController.text.length.toString(),
          labelStyle: TextStyle(
            fontSize: 12.5,
            fontStyle: FontStyle.italic,
          ),
          //helperText: 'Separate, with, commas',
          //floatingLabelBehavior: ,

          // filled: true,
          // fillColor: Colors.lightBlue.withOpacity(0.05),

          // BEGIN change border if focus
          focusedBorder: OutlineInputBorder(
            borderSide: BorderSide(color: Colors.lightBlue),
            borderRadius: BorderRadius.circular(20.0),
          ),
          // END change border if focus

          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20.0),
            borderSide: BorderSide(),
          ),
        ),
        textInputAction:
            TextInputAction.next, // moves user from one field to the next!!!!
        autofocus:
            false, // on screen load, first text field is already active - user can just start typing
      ),
    );
  
 //</formstate>`

var alertStyle = AlertStyle(
  animationType: AnimationType.fromTop,
  isCloseButton: true,
  isOverlayTapDismiss: true,
  descTextAlign: TextAlign.start,
  alertBorder: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(20.0),
    side: BorderSide(
      color: Colors.grey,
    ),
  ),
  titleStyle: TextStyle(
    fontWeight: FontWeight.normal,
    fontStyle: FontStyle.italic,
    fontSize: 16,
    color: Colors.black54,
  ),
  alertAlignment: Alignment.topCenter,
);

// BEGIN code from material_tag_editor
class _Chip extends StatelessWidget 
  const _Chip(
    @required this.label,
    @required this.onDeleted,
    @required this.index,
  );

  final String label;
  final ValueChanged<int> onDeleted;
  final int index;

  @override
  Widget build(BuildContext context) 
    return Chip(
      backgroundColor: Colors.blueGrey.shade100,
      labelPadding: const EdgeInsets.only(left: 8.0),
      label: Text(label),
      deleteIcon: Icon(
        Icons.cancel_rounded, // was "Icons.close,"
        size: 18,
      ),
      onDeleted: () 
        onDeleted(index);
      ,
    );
  

// END code from material_tag_editor

class SuperDivider extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Padding(
      padding: const EdgeInsets.only(
        top: 4.0,
        bottom: 4.0,
      ),
      child: const Divider(
        color: Colors.white70,
        height: 30,
        thickness: 0.1,
        indent: 0,
        endIndent: 0,
      ),
    );
  

【问题讨论】:

【参考方案1】:

Flutter 表单构建器假定只会使用子表单,只要它们的值发生更改,就会自动更新祖先表单。但是由于标签生成器不是表单小部件,您可以做两件事 -

    将这些小部件包装在一个新的表单小部件中,该小部件的职责是只向祖先窗体发送更新并呈现其子窗体。 你可以这样做
class GenericFormWidget<T> extends StatefulWidget 
  GenericFormWidget(
    Key key,
    @required this.attribute,
    @required this.builder,
  ) : super(key: key);

  final String attribute;
  final Function(BuildContext, Function(T value)) builder;

  @override
  Widget build(BuildContext context) 
    return widget.builder(context, _updateValue);
  

  void _updateValue(T value) =>
      _formState.setAttributeValue(widget.attribute, value);

    您可以使用 Flutter 表单并使用材质小部件创建自己的字段 UI,而不是使用 Flutter 表单构建器。这对您来说成本很高,因为您还需要迁移现有字段。

【讨论】:

您是否有使用 v4.x.x 对此的更新响应?

以上是关于如何将“material_tag_editor”中用户提交的标签数据添加到“Flutter Form Builder”表单中?的主要内容,如果未能解决你的问题,请参考以下文章

在MATLAB中如何将图导出

如何将datagridview中数据导出

C#中如何将两个项目合并到一个项目中

SQL中如何将两个相同的表格组合成一个表格

如何将excel导入到数据窗口中

如何将视频的每一帧提取出来