I have created a page where user can input their exam result which are subject and grade.
On the page, user need to click the add subject button to add another subject depending on how many subject they have. User also can remove subject by clicking the remove subject button.
The problem is only TextFormField value submitted. How to get the data for DropDownFormField if the form created dynamically?
I have tried the read the data using the onChanged function of the drop down and yes the data was stored to the variable gradeController but it returns null when I submit the data.
This is the model
class SubjectGrade {
final String? name;
final String? grade;
SubjectGrade(this.name, this.grade);
@override
String toString() {
return 'SPM: name = $name, grade = $grade';
}
}
This is the form
import 'package:flutter/material.dart';
import 'package:testing_app/models/education_model.dart';
class EducationBackgroundForm extends StatefulWidget {
const EducationBackgroundForm({Key? key}) : super(key: key);
@override
_EducationBackgroundFormState createState() =>
_EducationBackgroundFormState();
}
class _EducationBackgroundFormState extends State<EducationBackgroundForm> {
final nameTECs = <TextEditingController>[];
final gradeTECs = <String?>[];
final forms = <Form>[];
Form createForm() {
TextEditingController nameController = TextEditingController();
String? gradeController;
nameTECs.add(nameController);
gradeTECs.add(gradeController);
final grades = ['A ', 'A', 'A-', 'B ', 'B', 'C ', 'C', 'D', 'E', 'G'];
DropdownMenuItem<String> buildMenuItem(String item) => DropdownMenuItem(
value: item,
child: Text(item),
);
return Form(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: <Widget>[
Expanded(
flex: 2,
child: Column(
children: <Widget>[
TextFormField(
controller: nameController,
decoration: InputDecoration(
hintText: 'Subject Name',
labelText: 'Subject ${forms.length 1}',
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.only(left: 8, right: 8),
),
keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
),
],
),
),
const SizedBox(width: 5),
Expanded(
flex: 1,
child: Column(
children: <Widget>[
DropdownButtonHideUnderline(
child: DropdownButtonFormField(
value: gradeController,
isExpanded: true,
iconSize: 36,
icon: const Icon(Icons.arrow_drop_down,
color: Colors.black),
items: grades.map(buildMenuItem).toList(),
onChanged: (String? value) {
setState(() {
gradeController = value;
});
},
hint: const Text("Grade"),
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.only(left: 8),
),
),
)
],
)),
],
),
const SizedBox(height: 10)
],
),
);
}
@override
void initState() {
super.initState();
forms.add(createForm());
}
saveSPMResultToFirebase() {
List<SubjectGrade> entries = [];
for (int i = 0; i < forms.length; i ) {
final name = nameTECs[i].text;
final grade = gradeTECs[i].toString();
entries.add(SubjectGrade(name, grade));
}
debugPrint(entries.toString());
//Navigator.pop(context, entries);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text("SPM Result", style: TextStyle(fontSize: 18)),
const SizedBox(height: 20),
Expanded(
child: ListView.builder(
itemCount: forms.length,
itemBuilder: (BuildContext context, int index) {
return forms[index];
},
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8))),
onPressed: () => setState(() {
forms.add(createForm());
}),
child: const Text(
'Add Subject',
style: TextStyle(
fontSize: 16,
),
),
),
),
),
const SizedBox(width: 5),
Expanded(
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8))),
onPressed: () => setState(() {
if (forms.length > 1) {
forms.removeAt(forms.length - 1);
}
}),
child: const Text(
'Remove Subject',
style: TextStyle(
fontSize: 16,
),
),
),
),
),
],
),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8))),
onPressed: () {
saveSPMResultToFirebase();
},
child: const Text(
'Update',
style: TextStyle(
fontSize: 16,
),
),
),
),
],
);
}
}
When I submit the form, the debug output I get from the submit function for grade is null.
Output:
[SPM: name = English, grade = null]
CodePudding user response:
You should add value of DropDownButton in onChanged method not at top of create form.
change your code to this:
First Delete gradeTECs.add(gradeController); line from top of createForm().
Form createForm() {
TextEditingController nameController = TextEditingController();
String? gradeController;
nameTECs.add(nameController);
// gradeTECs.add(gradeController); <==== DELETE THIS LINE.
Add gradeTECs.add(gradeontroller); in onChanged method of DropdownButtonFormField
DropdownButtonHideUnderline(
child: DropdownButtonFormField(
value: gradeController,
isExpanded: true,
iconSize: 36,
icon: const Icon(Icons.arrow_drop_down,
color: Colors.black),
items: grades.map(buildMenuItem).toList(),
onChanged: (String? value) {
setState(() {
gradeController = value;
gradeTECs.add(gradeController); <=== ADD HERE ==>
});
},
hint: const Text("Grade"),
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.only(left: 8),
),
),
),
