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
I tried adding static to the global key ,but I couldn't because I couldn't wrap the key in a refreshIndicator.
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();
}
},
);
}
}
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:

After writing your title and description:

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

Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With