Flutter 多个小部件在颤振中使用了相同的globalkey错误

Flutter 多个小部件在颤振中使用了相同的globalkey错误,flutter,dart,Flutter,Dart,我试图解决这个问题,我在堆栈溢出中查找答案 但我还没有解决它 我在创建和更新页面中使用了全局键 我所做的 我尝试向全局键添加static,但做不到 因为我没法把钥匙包起来 我使用了Navigator PushName而不是Navigator push import'包装:flift/cupertino.dart'; 进口“包装:颤振/材料.省道”; 导入“包:cloud_firestore/cloud_firestore.dart”; 类更新扩展了StatefulWidget{ @凌驾 _Upd

我试图解决这个问题,我在堆栈溢出中查找答案

但我还没有解决它

我在创建和更新页面中使用了全局键

我所做的

  • 我尝试向全局键添加static,但做不到 因为我没法把钥匙包起来

  • 我使用了Navigator PushName而不是Navigator push

  • import'包装:flift/cupertino.dart';
    进口“包装:颤振/材料.省道”;
    导入“包:cloud_firestore/cloud_firestore.dart”;
    类更新扩展了StatefulWidget{
    @凌驾
    _UpdateState createState()=>\u UpdateState();
    }
    类_UpdateState扩展状态{
    GlobalKey _formKey1=GlobalKey(debugLabel:'u updateFormKey');
    TextEditingController _titleController1=TextEditingController();
    TextEditingController_descController1=TextEditingController();
    final db=Firestore.instance;
    文档快照_currentDocument;
    @凌驾
    小部件构建(构建上下文){
    最终大小=MediaQuery.of(context).Size;
    返回材料PP(
    家:脚手架(
    resizeToAvoidBottomInset:false,
    appBar:appBar(
    标题:文本(“更新”),
    ),
    正文:_buildUpdate(context));
    }
    小部件构建更新(构建上下文){
    最终大小=MediaQuery.of(context).Size;
    返回流生成器(
    流:db.collection('flatter_data2').snapshots(),
    生成器:(上下文,快照){
    if(snapshot.hasData){
    返回列(
    子项:快照.数据.文档.映射((doc){
    返回列(
    儿童:[
    填充物(
    填充:所有边缘设置(20.0),
    孩子:卡片(
    标高:2.0,
    形状:圆形矩形边框(
    边界半径:边界半径。圆形(16.0)),
    孩子:表格(
    键:_formKey1,
    孩子:填充(
    填充:仅限边设置(左:12,右:12),
    子:列(
    儿童:[
    TextFormField(
    控制器:\标题控制器1,
    装饰:输入装饰(标签文本:文档数据['title']),
    验证器:(字符串值){
    if(value.isEmpty){
    返回“title empty”;
    }否则{
    返回null;
    }
    },
    ),
    TextFormField(
    控制器:_descController1,
    装饰:输入装饰(labelText:doc.data['desc']),
    验证器:(字符串值){
    if(value.isEmpty){
    返回'desc empty';
    }否则{
    返回null;
    }
    },
    ),
    ],
    ),
    ),
    ),
    ),
    ),
    升起的按钮(
    形状:圆形矩形边框(
    边界半径:边界半径。圆形(15.0)),
    子项:文本('update'),
    颜色:颜色,蓝色,
    onPressed:()异步{
    if(_formKey1.currentState.validate()){
    分贝
    .collection('flatter_data2')
    .document(doc.documentID)
    .updateData({'title':_titleController1.text,'desc':_descController1.text});
    Navigator.pop(上下文);
    }
    },
    ),
    ],
    );
    }).toList(),
    );
    }否则{
    返回SizedBox();
    }
    },
    );
    }
    }
    
    您可能真的想在这里使用一些模块化。最好在不同的文件中使用自己的控制器集创建自定义表单小部件。这样,您就不必显式地管理控制器。还有一点需要注意的是,您的按钮对每个条目都执行相同的操作。在这种情况下,您还可以在自定义表单小部件中添加全局键,并在其中硬编码onPressed函数

    这里有一个例子

    // This is a mock data. Your firebase snapshot.data will have a similar structure
    List<Map<String, dynamic>> _mockData = [
      {
        'title':'Title 1',
        'desc':'Description 1',
      },
      {
        'title':'Title 2',
        'desc':'Description 2',
      },
      {
        'title':'Title 3',
        'desc':'Description 3',
      },
      {
        'title':'Title 4',
        'desc':'Description 4',
      },
    ];
    
    // There are many ways to make this work.
    // Instead of hardcoding the function in our custom form widget, We would like to pass a function implementation which will be called after the button in the form is pressed. This way we will have more control on what will happen when we press the button
    typedef onFormData = Future<void> Function(String, String); // Future void to allow async updates // The two strings are title and description respectively.
    
    
    // This is the custom form widget you need to create
    class MyForm extends StatefulWidget {
      final Map<String, dynamic> data; // Replace it with DocumentSnapshot data.
      final onFormData onPressed; // We will use the type we defined up there. So we will be expecting a function implementation here which takes two strings, a title and a description
    
      MyForm({@required this.data, @required this.onPressed, Key key}):super(key: key);
    
      @override
      createState() => _MyFormState();
    }
    
    // Our custom form widget is defined here
    class _MyFormState extends State<MyForm> {
    
      // Define the controllers
      TextEditingController _titleController; 
      TextEditingController _descController;
    
      // Create the key
      GlobalKey<FormState> _formKey;
    
      @override
      void initState() {
        // Initialize the values here
        super.initState();
        _titleController = TextEditingController();
        _descController = TextEditingController();
        _formKey = GlobalKey<FormState>();
      }
    
      @override
      void dispose() {
        // Remember that you have to dispose of the controllers once the widget is ready to be disposed of
        _titleController.dispose();
        _descController.dispose();
        _formKey = null;
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        // Everything remains almost same here as in your code
        return Column(
          children: <Widget>[
            Padding(
              padding: EdgeInsets.all(20.0),
              child: Card(
                elevation: 2.0,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(16.0)),
                child: Form(
                  key: _formKey,
                  child: Padding(
                    padding: EdgeInsets.only(left: 12, right: 12),
                    child: Column(
                      children: <Widget>[
                        TextFormField(
                          controller: _titleController, // Assign the controller
                          decoration:
                              InputDecoration(labelText: widget.data['title']), // widget.data can still be indexed like this after you replace datatype of the data to DocumentSnapshot
                          validator: (String value) {
                            if (value.isEmpty) {
                              return 'title is empty';
                            } else {
                              return null;
                            }
                          },
                        ),
                        TextFormField(
                          controller: _descController,
                          decoration:
                              InputDecoration(labelText: widget.data['desc']), // Same goes here
                          validator: (String value) {
                            if (value.isEmpty) {
                              return 'description is empty';
                            } else {
                              return null;
                            }
                          },
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
            // The button associated with this form
            RaisedButton(
              shape:
                  RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
              child: Text('Update'),
              color: Colors.blue,
              onPressed: () async {
                // If validation is successful, then call the on pressed function we assigned to the widget. // Check the MyWidget class
                if (_formKey.currentState.validate()) {
                  await widget.onPressed(_titleController.text, _descController.text); // Instead of putting firebase update code here, we are passing the title and description to our main widget from where we will post
                }
              },
            ),
          ],
        );
      }
    }
    
    // Our main widget
    class MyWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Demo'),
          ),
          // Wrap this up in your stream builder
          // I am using a listview with mock data for the sake of this example.
          body: ListView.builder(
          itemBuilder: (context, index) {
            // We create a new instance of our custom form and we don't need to manage any controllers or keys. We just need to pass the data and what happens when we press the update button in our custom form.
            // Here is why we defined a type named onFormData before.
            // You can simply post updates in your form widget directly if your logic is same for each form
            // We are getting the title and description info here through our custom defined Forms without managing any keys and controllers.
            // Also this method is async so you can post your firebase updates from here waiting for them to complete using await
            return MyForm(data: _mockData[index], onPressed: (String title, String description) async {
              // Put your firebase update code here
              _mockData[index]['title'] = title;
              _mockData[index]['desc'] = description;
              Navigator.of(context).pop(); // Go back after the updates are made as written in your example
            });
          },
          physics: BouncingScrollPhysics(),
          itemCount: _mockData.length, // Length of the data.
            ),
        );
      }
    }
    
    //这是一个模拟数据。您的firebase snapshot.data将具有类似的结构
    列表_mockData=[
    {
    “标题”:“标题1”,
    'desc':'Description 1',
    },
    {
    “标题”:“标题2”,
    'desc':'Description 2',
    },
    {
    “标题”:“标题3”,
    “描述”:“描述3”,
    },
    {
    “标题”:“标题4”,
    'desc':'Description 4',
    },
    ];
    //有很多方法可以使这项工作。
    //我们希望传递一个函数实现,而不是在自定义表单小部件中对函数进行硬编码,该函数实现将在按下表单中的按钮后调用。这样,当我们按下按钮时,我们将有更多的控制权
    typedef onFormData=未来函数(字符串,字符串);//Future void允许异步更新//这两个字符串分别是title和description。
    //这是您需要创建的自定义表单小部件
    类MyForm扩展了StatefulWidget{
    最终地图数据;//将其替换为DocumentSnapshot数据。
    final onFormData onPressed;//我们将使用上面定义的类型。因此,我们希望在这里实现一个函数,它包含两个字符串,一个标题和一个描述
    MyForm({@required this.da)
    
    // This is a mock data. Your firebase snapshot.data will have a similar structure
    List<Map<String, dynamic>> _mockData = [
      {
        'title':'Title 1',
        'desc':'Description 1',
      },
      {
        'title':'Title 2',
        'desc':'Description 2',
      },
      {
        'title':'Title 3',
        'desc':'Description 3',
      },
      {
        'title':'Title 4',
        'desc':'Description 4',
      },
    ];
    
    // There are many ways to make this work.
    // Instead of hardcoding the function in our custom form widget, We would like to pass a function implementation which will be called after the button in the form is pressed. This way we will have more control on what will happen when we press the button
    typedef onFormData = Future<void> Function(String, String); // Future void to allow async updates // The two strings are title and description respectively.
    
    
    // This is the custom form widget you need to create
    class MyForm extends StatefulWidget {
      final Map<String, dynamic> data; // Replace it with DocumentSnapshot data.
      final onFormData onPressed; // We will use the type we defined up there. So we will be expecting a function implementation here which takes two strings, a title and a description
    
      MyForm({@required this.data, @required this.onPressed, Key key}):super(key: key);
    
      @override
      createState() => _MyFormState();
    }
    
    // Our custom form widget is defined here
    class _MyFormState extends State<MyForm> {
    
      // Define the controllers
      TextEditingController _titleController; 
      TextEditingController _descController;
    
      // Create the key
      GlobalKey<FormState> _formKey;
    
      @override
      void initState() {
        // Initialize the values here
        super.initState();
        _titleController = TextEditingController();
        _descController = TextEditingController();
        _formKey = GlobalKey<FormState>();
      }
    
      @override
      void dispose() {
        // Remember that you have to dispose of the controllers once the widget is ready to be disposed of
        _titleController.dispose();
        _descController.dispose();
        _formKey = null;
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        // Everything remains almost same here as in your code
        return Column(
          children: <Widget>[
            Padding(
              padding: EdgeInsets.all(20.0),
              child: Card(
                elevation: 2.0,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(16.0)),
                child: Form(
                  key: _formKey,
                  child: Padding(
                    padding: EdgeInsets.only(left: 12, right: 12),
                    child: Column(
                      children: <Widget>[
                        TextFormField(
                          controller: _titleController, // Assign the controller
                          decoration:
                              InputDecoration(labelText: widget.data['title']), // widget.data can still be indexed like this after you replace datatype of the data to DocumentSnapshot
                          validator: (String value) {
                            if (value.isEmpty) {
                              return 'title is empty';
                            } else {
                              return null;
                            }
                          },
                        ),
                        TextFormField(
                          controller: _descController,
                          decoration:
                              InputDecoration(labelText: widget.data['desc']), // Same goes here
                          validator: (String value) {
                            if (value.isEmpty) {
                              return 'description is empty';
                            } else {
                              return null;
                            }
                          },
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
            // The button associated with this form
            RaisedButton(
              shape:
                  RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
              child: Text('Update'),
              color: Colors.blue,
              onPressed: () async {
                // If validation is successful, then call the on pressed function we assigned to the widget. // Check the MyWidget class
                if (_formKey.currentState.validate()) {
                  await widget.onPressed(_titleController.text, _descController.text); // Instead of putting firebase update code here, we are passing the title and description to our main widget from where we will post
                }
              },
            ),
          ],
        );
      }
    }
    
    // Our main widget
    class MyWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Demo'),
          ),
          // Wrap this up in your stream builder
          // I am using a listview with mock data for the sake of this example.
          body: ListView.builder(
          itemBuilder: (context, index) {
            // We create a new instance of our custom form and we don't need to manage any controllers or keys. We just need to pass the data and what happens when we press the update button in our custom form.
            // Here is why we defined a type named onFormData before.
            // You can simply post updates in your form widget directly if your logic is same for each form
            // We are getting the title and description info here through our custom defined Forms without managing any keys and controllers.
            // Also this method is async so you can post your firebase updates from here waiting for them to complete using await
            return MyForm(data: _mockData[index], onPressed: (String title, String description) async {
              // Put your firebase update code here
              _mockData[index]['title'] = title;
              _mockData[index]['desc'] = description;
              Navigator.of(context).pop(); // Go back after the updates are made as written in your example
            });
          },
          physics: BouncingScrollPhysics(),
          itemCount: _mockData.length, // Length of the data.
            ),
        );
      }
    }