Reputation: 527
My Flutter form contains a TextFormField for Name. When my user clicks on it, the entire form immediately re-renders (reloads), making it impossible for my user to enter anything.
The TextFormField is commented so that you can easily find it.
You'll notice that this page also contains a second field that works perfectly. It's a Switch inside a StatefulBuilder that handles setting a TextEditingController for _importantController.
<!-- language: flutter -->
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
class CrudPage2 extends StatefulWidget {
final String docId;
const CrudPage2({Key? key, required this.docId}) : super(key: key);
@override
CrudPage2State createState() => CrudPage2State();
}
class CrudPage2State extends State<CrudPage2> {
late String name = "";
late bool isImportant = false;
final TextEditingController _nameController = TextEditingController();
final TextEditingController _importantController = TextEditingController();
Stream<DocumentSnapshot<Object?>> groceryItem(docID) =>
FirebaseFirestore.instance
.collection("groceries")
.doc(docID)
.snapshots();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
title: Text("Grocery Item"),
),
body: SizedBox(
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom + 20),
child: StreamBuilder<DocumentSnapshot>(
stream: groceryItem(widget.docId),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
print("****** Loading ******"); // debugging
return const Text("Loading");
} else if (streamSnapshot.hasData) {
if (widget.docId != "NEW") {
// Retrieve existing item
var jsonData = streamSnapshot.data?.data();
Map<String, dynamic> myData = jsonData as Map<String, dynamic>;
name = myData['name'];
isImportant = myData['important'];
}
_nameController.text = name;
if (isImportant) {
_importantController.text = "true";
} else {
_importantController.text = "false";
}
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//--------------------------------------------------------
// PROBLEM: Clicking on this field re-renders entire form.
Flexible(
child: TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
),
//--------------------------------------------------------
// No problem with this switch
StatefulBuilder(
builder: (BuildContext context, StateSetter importantStateSetter) {
return Row(
children: [
const Text("Important: "),
Switch(
value: isImportant,
onChanged: (value) {
importantStateSetter(() => isImportant = value);
},
),
],
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
child: ElevatedButton(
child: const Text('Cancel'),
onPressed: () async {
Navigator.of(context).pop();
}),
),
const SizedBox(
width: 10,
),
SizedBox(
child: ElevatedButton(
child: const Text("Submit"),
onPressed: () async {
final String name = _nameController.text;
if (widget.docId == 'NEW') {
addGroceryItem(name, 1.0, "test",
isImportant);
} else {
updateGroceryItem(widget.docId, name,
1.0, "test", isImportant);
}
// Clear the text fields
_nameController.text = '';
_importantController.text = "";
// Hide the bottom sheet
Navigator.of(context).pop();
},
),
)
],
),
],
);
} else {
return const Text("No Data");
}
})
),
),
);
} // Widget Build
//-------------------------------------------------------------
// Add New Grocery Item
//-------------------------------------------------------------
Future<void> addGroceryItem(
String name, double quantity, String category, bool isImportant) async {
await FirebaseFirestore.instance.collection('groceries').add({
"active": true,
"name": name,
"quantity": quantity,
"category": category,
"important": isImportant
});
}
//-------------------------------------------------------------
// Update Existing Grocery Item
//-------------------------------------------------------------
Future<void> updateGroceryItem(String docID, String name, double quantity,
String category, bool isImportant) async {
await FirebaseFirestore.instance.collection('groceries').doc(docID).update({
"active": true,
"name": name,
"quantity": quantity,
"category": category,
"important": isImportant
});
}
}
I added print("****** Loading ******");
line to help debug. When user clicks on text form field, the Console displays:
I/flutter (28767): ****** Loading ******
I/flutter (28767): ****** Loading ******
Why is the stream refreshing every time this widget is clicked?
Upvotes: 0
Views: 598
Reputation: 527
After a lot of Googling, I decided that my problem was coming from doing this entirely wrong. Here are some of the changes I made:
Code below solves my problem using these changes:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class CrudPage2 extends StatefulWidget {
final String docId;
final Object? docSnap;
const CrudPage2({Key? key,
required this.docId,
required this.docSnap})
: super(key: key);
@override
CrudPage2State createState() => CrudPage2State();
}
class CrudPage2State extends State<CrudPage2> {
//--- Form State Variables...
late String name = "";
late bool isImportant = false;
//--- Controllers for Form Fields...
final TextEditingController _nameController = TextEditingController();
final TextEditingController _importantController = TextEditingController();
@override
initState() {
super.initState();
if (widget.docId != "NEW") {
Map<String, dynamic> myData = widget.docSnap as Map<String, dynamic>;
name = myData['name'];
isImportant = myData['important'];
}
_nameController.text = name;
if (isImportant) {
_importantController.text = "true";
} else {
_importantController.text = "false";
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
title: Text("Grocery Item"),
),
body: SizedBox(
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(
top: 20,
left: 20,
right: 20,
bottom: MediaQuery.of(context).viewInsets.bottom + 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
StatefulBuilder(
builder:
(BuildContext context, StateSetter importantStateSetter) {
return Row(
children: [
const Text("Important: "),
Switch(
value: isImportant,
onChanged: (value) {
importantStateSetter(() => isImportant = value);
},
),
],
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 110,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(
Colors.grey),
padding: MaterialStateProperty.all(
const EdgeInsets.all(5)),
textStyle: MaterialStateProperty.all(
const TextStyle(fontSize: 24))),
child: const Text('Cancel'),
onPressed: () async {
Navigator.of(context).pop();
}),
),
SizedBox(
width: 200,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.green),
padding: MaterialStateProperty.all(
const EdgeInsets.all(5)),
textStyle: MaterialStateProperty.all(
const TextStyle(fontSize: 24))),
child: const Text("Submit"),
onPressed: () async {
final String name = _nameController.text;
if (widget.docId == 'NEW') {
addGroceryItem(name, 1.0, "Test",
isImportant);
} else {
updateGroceryItem(widget.docId, name,
1.0, "Test", isImportant);
}
// Clear the text fields
_nameController.text = '';
_importantController.text = "";
// Hide the bottom sheet
Navigator.of(context).pop();
},
),
)
],
),
]
)
)
),
);
} // Widget Build
Future<void> addGroceryItem(
String name, double quantity, String category, bool isImportant) async {
await FirebaseFirestore.instance.collection('groceries').add({
"active": true,
"name": name,
"quantity": quantity,
"category": category,
"important": isImportant
});
}
Future<void> updateGroceryItem(String docID, String name, double quantity,
String category, bool isImportant) async {
await FirebaseFirestore.instance.collection('groceries').doc(docID).update({
"active": true,
"name": name,
"quantity": quantity,
"category": category,
"important": isImportant
});
}
}
Any comments and/or suggestions are still appreciated.
I hope this helps someone else in the future.
Upvotes: 1