【问题标题】:Multiple widgets used the same globalkey error in flutter多个小部件在颤动中使用了相同的全局键错误
【发布时间】:2020-06-02 10:49:33
【问题描述】:

我试图解决这个问题,我在 Stack Overflow 中查找了答案

但是我还没解决

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

我做了什么

  1. 我尝试将静态添加到全局键,但我不能 因为我无法将密钥包装在 refreshIndicator 中。

  2. 我使用 Navigator pushNamed 而不是 Navigator push

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class Update extends StatefulWidget {
  @override
  _UpdateState createState() => _UpdateState();
}

class _UpdateState extends State<Update> {
  GlobalKey<FormState> _formKey1 = GlobalKey<FormState>(debugLabel: '_updateFormKey');

  TextEditingController _titleController1 = TextEditingController();
  TextEditingController _descController1 = TextEditingController();
  final db = Firestore.instance;
  DocumentSnapshot _currentDocument;


  @override
  Widget build(BuildContext context) {
    final Size size = MediaQuery.of(context).size;

    return MaterialApp(
        home: Scaffold(
            resizeToAvoidBottomInset: false,
            appBar: AppBar(
              title: Text('update'),
            ),
            body: _buildUpdate(context)));
  }

  Widget _buildUpdate(BuildContext context) {
    final Size size = MediaQuery.of(context).size;


    return StreamBuilder<QuerySnapshot>(
      stream: db.collection('flutter_data2').snapshots(),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Column(
            children: snapshot.data.documents.map<Widget>((doc) {
              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: _formKey1,
                        child: Padding(
                          padding: EdgeInsets.only(left: 12, right: 12),
                          child: Column(
                            children: <Widget>[
                              TextFormField(
                                controller: _titleController1,
                                decoration: InputDecoration(labelText: doc.data['title']),
                                validator: (String value) {
                                  if (value.isEmpty) {
                                    return 'title empty';
                                  } else {
                                    return null;
                                  }
                                },
                              ),
                              TextFormField(
                                controller: _descController1,
                                decoration: InputDecoration(labelText: doc.data['desc']),
                                validator: (String value) {
                                  if (value.isEmpty) {
                                    return 'desc empty';
                                  } else {
                                    return null;
                                  }
                                },
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ),
                  RaisedButton(
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(15.0)),
                    child: Text('update'),
                    color: Colors.blue,
                    onPressed: () async {
                      if (_formKey1.currentState.validate()) {
                        db
                            .collection('flutter_data2')
                            .document(doc.documentID)
                            .updateData({'title': _titleController1.text,'desc':_descController1.text});
                        Navigator.pop(context);
                      }
                    },
                  ),
                ],
              );
            }).toList(),
          );
        } else {
          return SizedBox();
        }
      },
    );
  }
}

【问题讨论】:

  • 您遇到什么错误?提供有关问题的更多详细信息
  • 多个widget使用同一个globalkey出现错误
  • 我猜是因为我在创建页面中也创建了form和formkey
  • 您正在映射快照数据文档,该文档为每个表单小部件添加相同的键,这就是为什么它给出错误多个小部件使用相同的全局键。
  • 哦,我了解机制谢谢

标签: flutter dart


【解决方案1】:

您可能真的想在这里使用一些模块化。最好在具有自己的一组控制器的不同文件中创建自定义表单小部件。这样您就不必显式地管理控制器。还有一点需要注意的是,您的 Button 对每个条目都执行相同的工作。在这种情况下,您不妨在自定义表单小部件中添加全局键并在其中硬编码 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.
        ),
    );
  }
}

在任何更新之前:

写完你的标题和描述后:

按下更新后,当你回到同一屏幕时:

希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-21
    • 2021-04-03
    • 2019-12-31
    • 1970-01-01
    • 2021-02-11
    • 1970-01-01
    • 1970-01-01
    • 2020-11-29
    相关资源
    最近更新 更多