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

时间:2020-02-18 12:03:33

标签: flutter dart

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

但是我还没有解决

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

我所做的

  1. 我尝试将static添加到全局密钥,但是我无法 因为我无法将密钥包装在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();
        }
      },
    );
  }
}

1 个答案:

答案 0 :(得分:5)

您可能真的想在这里使用一些模块化。最好在具有其自己的控制器集的其他文件中创建自定义Form小部件。这样,您将不必显式管理控制器。需要注意的另一件事是,您的Button对于每个条目都执行相同的工作。在这种情况下,您最好在自定义Form小部件内添加全局密钥,然后在其中对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.
        ),
    );
  }
}

在进行任何更新之前:

Before update

写完标题和描述后:

Write your title and description

按更新后,当您返回同一屏幕时:

After pressing update, when you come back to the screen

希望这会有所帮助!