Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple widgets used the same globalkey error in flutter

Tags:

flutter

dart

I tried to solve this, I looked up the answers in Stack Overflow

But I haven't solved it yet

I used the global key in the create and update pages

What I've done

  1. I tried adding static to the global key ,but I couldn't because I couldn't wrap the key in a refreshIndicator.

  2. I used Navigator pushNamed instead of 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();
        }
      },
    );
  }
}
like image 870
Kys M60 Avatar asked Aug 07 '26 00:08

Kys M60


1 Answers

You might really want to use some modularity here. Create your custom Form widget preferably in a different file with their own set of controllers. This way you won't have to manage the controllers explicitly. One more thing to notice is that your Button is doing the same job for every entry. In this case, you might as well add the global key inside your custom Form widget and hardcode the onPressed function there.

Here is an example

// 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 any updates:

Before update

After writing your title and description:

Write your title and description

After pressing update, when you go back to the same screen:

After pressing update, when you come back to the screen

Hope this helps!

like image 186
Afridi Kayal Avatar answered Aug 10 '26 11:08

Afridi Kayal



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!