DropdownButton doesn't change value and unable to be reused - flutter

I created a method for Dropdown button to reuse the code. Whenever the method is called, the button does not update. But the value updates in the console updates when I tried to debug. Even though I created the method in Stateful widget, setState method doesn't seem to work!
Here's the code and a couple of screenshots
class _SGPAState extends State<SGPA> {
List<String> _credits = ['0', '1', '2', '3', '4', '5'];
List<String> _grades = ['O', 'A+', 'A', 'B+', 'B', 'C', 'P', 'F', 'Ab', 'I'];
List<String?> selectedCredit = List.filled(15, '0');
List<String?> selectedGrade = List.filled(15, 'O');
#override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text('SNo', style: kGPATextStyle),
Text('Credits', style: kGPATextStyle),
Text('Grade', style: kGPATextStyle),
],
),
SizedBox(
height: 15,
),
dataRow(index: 0),
dataRow(index: 1),
dataRow(index: 2),
dataRow(index: 3),
dataRow(index: 4),
],
);
}
Row dataRow({required int index}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'${index+1}',
style: kGPATextStyle,
),
buildDropdown(selectedValue: selectedCredit[index], selectedList: _credits),
buildDropdown(selectedValue: selectedGrade[index], selectedList: _grades),
],
);
}
DropdownButton<String> buildDropdown(
{required String? selectedValue, required List<String> selectedList}) {
return DropdownButton(
value: selectedValue,
items: selectedList.map((location) {
return DropdownMenuItem(
value: location,
child: Text(
location,
style: kDropdownTextStyle,
),
);
}).toList(),
onChanged: (String? newValue) {
selectedValue = newValue;
setState(() {
selectedValue = newValue;
});
print(selectedValue);
},
);
}
}
Dropdown buttons screenshot
Console screenshot
I'm stuck with this for quite some time.
Can anyone tell me the reason for this issue? Or is there a solution?

You are assigning to the local variable selectedValue, which isn't persisted from one build to another. So if you want the value to be saved across builds, you have to assign it to an attribute on the state class (like selectedCredit/selectedGrade).
Here's a possible solution to your problem:
Row dataRow({required int index}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'${index + 1}',
style: kGPATextStyle,
),
buildDropdown(
selectedValue: selectedCredit[index],
onSelected: (v) => setState(() {
selectedCredit[index] = v;
}),
selectedList: _credits,
),
buildDropdown(
selectedValue: selectedGrade[index],
onSelected: (v) => setState(() {
selectedGrade[index] = v;
}),
selectedList: _grades,
),
],
);
}
DropdownButton<String> buildDropdown({
required final String? selectedValue,
required final ValueSetter<String?> onSelected,
required final List<String> selectedList,
}) {
return DropdownButton(
value: selectedValue,
items: selectedList.map((location) {
return DropdownMenuItem(
value: location,
child: Text(
location,
style: kDropdownTextStyle,
),
);
}).toList(),
onChanged: (String? newValue) {
onSelected(newValue);
},
);
}

Related

How to get DropdownButtonFormField value with a button click - Flutter

I am trying to develop a survey form using Flutter and I have multiple dropdown fields in the form. I want to get the selected values from those dropdowns when I click the save button. But all I am getting is the value I initially set inside initState(). The code I am using is as below. Any help to get this sorted out is much appreciated.
class _EditSurveyState extends State<EditSurvey> {
String contactMethod;
String country;
List contactMethodList = ['phone', 'email', 'mail'];
List countryList = ['us', 'uk', 'germany'];
#override
void initState() {
super.initState();
contactMethod = surveryData['contact'];
country = surveryData['country'];
}
#override
Widget build(BuildContext context) {
return Scaffold(
return Scaffold(
children: [
Expanded(
flex: screenWidth(context) < 1300 ? 10 : 8,
child: SafeArea(
child: Column(
children: [
createDropdownField("Contact", contactMethod, contactMethodList),
createDropdownField("Country", country, countryList),
Row(mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () async {
print(contactMethod + country);
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)
)
),
child: Text(
"UPDATE",
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
fontWeight: FontWeight.bold,
),
)
),
],
),
]
)
)
)
]
)
);
}
Row createDropdownField(String labelText, String _valueChoose, List valueList) {
return Row (
children: [
SizedBox(height: 25,),
Align(
alignment: Alignment.centerLeft,
child: Text(
'$labelText',
),
),
DropdownButtonFormField(
value: _valueChoose,
hint: Text("$labelText"),
icon: Icon(Icons.arrow_drop_down),
isExpanded: true,
onChanged: (newValue){
setState(() {
_valueChoose = newValue;
});
},
items: valueList.map((valueItem){
return DropdownMenuItem(
value: valueItem,
child: Text(valueItem),
);
}).toList(),
),
],
);
}
}
I don't understand why you using intitstate if you want to initialize value to String you can do it while declaring, try removing initstate and
Declare a variable first where you will store new value from dropdown onchange
i.e
class _EditSurveyState extends State<EditSurvey> {
String _currentValue;
DropdownButtonFormField(
onChanged: (val) =>
setState(() => _currentValue = val as String),
value: _currentValue ,
items: YourList.map((item) {
return DropdownMenuItem(
value: item,
child: Text('$item Items'),
);
}).toList(),
),

Passing onChanged Function to Radio results in 'Closure call with mismatched arguments'

I have the following build method in my stateful widget:
#override
Widget build(BuildContext context) {
return Column(children: [
Padding(
child: Container(
child: Row(
children: <Widget>[
_myRadioButton(
title: genders[0],
value: genders[0],
onChanged: (newValue) =>
setState(() => {_groupValue = newValue, context.read<UserSignupForm>().gender = newValue}),
),
_myRadioButton(
title: genders[1],
value: genders[1],
onChanged: (newValue) =>
setState(() => {
_groupValue = newValue, context.read<UserSignupForm>().gender = newValue}),
),
],
),
))
]);
}
And this is my Radio row:
Row _myRadioButton({required String title, String? value, required Function onChanged}) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Radio(
value: value,
groupValue: _groupValue,
onChanged: onChanged(),
),
Text(title)
],
);
}
However, I get the following runtime error when building the widget:
The following NoSuchMethodError was thrown building GenderField(dirty, state: _GenderFieldState#17448):
Closure call with mismatched arguments: function '_GenderFieldState.build.<anonymous closure>'
Receiver: Closure: (dynamic) => void
Tried calling: _GenderFieldState.build.<anonymous closure>()
Found: _GenderFieldState.build.<anonymous closure>(dynamic) => void
Any ideas how to correctly pass the onChanged method argument to the onChanged property?
Here is a solution with no compilation/runtime errors:
Row _myRadioButton({required String title, String? value,
required Function(dynamic)? onChanged}) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Radio(
value: value,
groupValue: _groupValue,
onChanged: onChanged,
),
Text(title)
],
);
}
remove the parentheses when passing the function
Row _myRadioButton({required String title, String? value, required void Function(String?) onChanged}) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Radio(
value: value,
groupValue: _groupValue,
onChanged: onChanged,
),
Text(title)
],
);
}

How can I add a button inside a dropdownButton in Flutter

I just started learning flutter, I have programming knowledge before, but I am just getting used to the widget structure. What I want to do is add a button to the last element of the DropdownButton list and I used the DropdownMenuItem widget to do that. And I used Text as child and I put Button in the last element. The problem is, I can't give Text to the value property of the DropdownButton. That's why I'm getting an error because I've given a value that isn't in items [ ]. Is there any way I can get the value of the Text widget? Or can I do it another way? I hope I was explanatory.
code:
class _TodoPageState extends State<TodoPage> {
Text dropdownValue = Text('123'); // **FOR DEFAULT VALUE**
#override
Widget build(BuildContext context) {
return SafeArea(
child: Center(
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
MyTabBar(),
Row(
children: [
Expanded(
child: Container(
margin: EdgeInsets.only(left: 3, top: 5),
child: Row(
children: [
Ink(
width: 152,
height: 45,
padding: EdgeInsets.all(6),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 2),
borderRadius: BorderRadius.circular(10),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: dropdownValue, **// CANT SET THE DEFAULT VALUE**
isExpanded: true,
icon: Image.asset('assets/down-list-arrow.png'),
iconSize: 10,
elevation: 16,
onChanged: (newValue) {
setState(() {
dropdownValue = newValue!; **// CANT SET THE DEFAULT VALUE**
});
},
items: [
DropdownMenuItem(child: Text('123'), value: ''),
DropdownMenuItem(child: Text('123'), value: ''),
DropdownMenuItem(
child: TextButton(
child: Text('Create'),
onPressed: () {},
))
],
),
),
)
],
),
),
),
],
),
MyListView()
],
),
));
}
}
I found 2 ways: Assigning create on values or check new value before assigning on onChanged and using FocusNode.
Test Widget
class TodoPage extends StatefulWidget {
TodoPage({Key? key}) : super(key: key);
#override
_TodoPageState createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage> {
late String selectedValue; // **FOR DEFAULT VALUE**
late String selectedValue2;
List<String> dropDownItemValue = ['123', '2', '4', 'Create'];
List<String> dropDownItemValue2 = ['xx', '2', '4'];
late final dropDownKey2;
final FocusNode dropDownFocus = FocusNode();
#override
void initState() {
super.initState();
///selected value must be contain at dropDownItemValue
selectedValue = dropDownItemValue[0];
selectedValue2 = dropDownItemValue2[0];
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.deepPurple,
body: Center(
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
// MyTabBar(),
DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: selectedValue, // CANT SET THE DEFAULT VALUE**
isExpanded: true,
// icon: Image.asset('assets/down-list-arrow.png'),
iconSize: 10,
elevation: 16,
onChanged: (newValue) {
print(newValue);
setState(() {
selectedValue = newValue!; // SET THE DEFAULT VALUE**
});
},
/// dont assing same value on multiple widget
items: List.generate(
dropDownItemValue.length,
(index) => DropdownMenuItem(
child: Text('${dropDownItemValue[index]}'),
value: '${dropDownItemValue[index]}'),
),
),
),
SizedBox(
height: 100,
),
DropdownButtonHideUnderline(
child: DropdownButton<String>(
focusNode: dropDownFocus,
value: selectedValue2, // CANT SET THE DEFAULT VALUE**
isExpanded: true,
// icon: Image.asset('assets/down-list-arrow.png'),
iconSize: 10,
elevation: 16,
onChanged: (newValue) {
print(newValue == null);
// if value doesnt contain just close the dropDown
if (newValue == null) {
dropDownFocus.unfocus();
} else
setState(() {
selectedValue2 = newValue; // SET THE DEFAULT VALUE**
});
},
/// dont assing same value on multiple widget
items: List.generate(
dropDownItemValue2.length + 1,
(index) => index < dropDownItemValue2.length
? DropdownMenuItem(
child: Text('${dropDownItemValue2[index]}'),
value: '${dropDownItemValue2[index]}')
: DropdownMenuItem(
child: TextButton(
child: Text('Create'),
onPressed: () {},
),
),
),
),
),
],
),
),
));
}
}

How to handle the list of dynamic dropdown in flutter?

I need to have the list of add able dropdown of states,district. They can be added. I could add the forms but I need to set the district data according to the states.I get the states from the api. When selecting the states from dropdown I get the districts according to states from api.Its working fine with the district dropdown of index 0 but I am getting the error There should be exactly one item with [DropdownButton]'s value: 1. in other index. How can I achieve this. I followed this https://stackoverflow.com/a/63188955/8023701. I have implemented as follows:
Widget _buildStates(BuildContext context, int i) {
return
Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
state,
style: TextStyle(
// fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.red),
),
SizedBox(
width: 20,
),
DropdownButtonFormField(
validator: (value) => validateDrops(value),
isExpanded: true,
hint: Text(state),
value: _selectedState[i],
onChanged: (newValue) {
setState(() {
print("Stae value");
print(newValue);
_selectedState[i]= newValue;
getMyDistricts(newValue, i);
});
},
items: statess.map((Data item) {
return new DropdownMenuItem(
child: new Text(
item.provinceNepali,
),
value: item.id.toString(),
);
}).toList(),
)
],
));
}
District widget:
Widget _buildDistrict(BuildContext context, int i) {
return Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
district,
style: TextStyle(
// fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.red),
),
SizedBox(
width: 20,
),
mydis != null
? DropdownButtonFormField(
validator: (value) => validateDrops(value),
isExpanded: true,
hint: Text(select),
value: _selectedDistrict[i],
onChanged: (newValue) {
setState(() {
_selectedDistrict[i] = newValue;
getMyMuni(newValue, i);
});
},
items: mydis.map((Datas item) {
return new DropdownMenuItem(
child: new Text(
item.municipalityEnglish,
),
value: item.id.toString(),
);
}).toList(),
)
: Container()
],
));
}
I am getting states from api as follows:
void getAvgProvince() async {
setState(() {
_isLoading = true;
});
ProvinceResponse joinResponse = await getProvince();
if (joinResponse != null) {
setState(() {
statess = joinResponse.data;
_isLoading = false;
});
} else {
setState(() {
_isLoading = false;
});
}
}
And district as follows:
void getMyDistricts(newValue, int i) async {
DistrictResponse joinResponse = await getDistrict(newValue);
if (joinResponse != null) {
setState(() {
mydis = joinResponse.data;
_isLoading = false;
});
} else {
setState(() {
_isLoading = false;
});
}
}
The DropdownButton error There should be exactly one item with [DropdownButton]'s value: 1 occurs because the initial selected value of the dropdown is null. What you can do here is add a placeholder value for the dropdown if it's yet to be active. You can follow a similar approach on this answer.

List view radio buton not selecting when selected

When i run the program on a device when i tick 2nd or 3rd term it does not take effect.
I am developing an Electronic student attendance tracking system so i decided to use a radio to track the term and also use check box to track the attendance that is checked if the student is present and unchecked if the student is not present but when i check the term radio it gives the correct output on the console but does not take effect on the physical screen.
import 'package:flutter/material.dart';
import 'package:atttendance_register/dataFiles/pupils.dart';
import 'package:atttendance_register/dataFiles/attendance.dart';
import 'package:intl/intl.dart';
class attendance extends StatefulWidget {
static Future<void> show(BuildContext context) async {
await Navigator.of(context).push(
MaterialPageRoute(builder: (context)=>attendance(),fullscreenDialog: true),
);
}
#override
_attendanceState createState() => _attendanceState();
}
class _attendanceState extends State<attendance> {
// final List<Pupils> pupils =[
// Pupils('John', ' Doe', 'Brad', 'Male', '001', DateTime.now(), '21'),
// Pupils('Jane', ' Doe', 'Mary', 'Female', '002', DateTime.now(), '21'),
// Pupils('Mohamed', ' James', '', 'Male', '003', DateTime.now(), '33'),
// Pupils('Titus', ' Nabieu', 'Jusu', 'Male', '004', DateTime.now(), '21'),
// Pupils('Steven', ' kai', 'Rogers', 'Male', '005', DateTime.now(), '21'),
// Pupils('Josephine', ' Bah', 'Neneh', 'Female', '006', DateTime.now(), '23')
//
// ];
final List<Attendance> attendance =[
Attendance(false,'John Doe Brad',DateTime.now(),0),
Attendance(true,'Jane Doe Mary',DateTime.now(),2),
Attendance(false,'Mohamed James',DateTime.now(),1),
Attendance(false,'Titus Nabieu Jusu',DateTime.now(),2),
Attendance(false,'Steven kai Rogers',DateTime.now(),2),
Attendance(false,'Josephine Bah Neneh',DateTime.now(),1)
];
bool selectedCheck = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Enter Attendance'),
backgroundColor: Colors.blue[900],
),
backgroundColor: Colors.blue[100],
body:Container(
child: ListView.builder(
itemCount:attendance.length,
itemBuilder:(BuildContext context, int index){
int selectedRadio = attendance[index].Term;
bool selectedCheck = attendance[index].attendance;
return Container(
child: Card(
child: Column(
//final pupil =pupils[index];
children: <Widget>[
Text(attendance[index].pupilName),
Text('Select Term'),
Row(
children: <Widget>[
Radio(
value:0,
groupValue: selectedRadio,
activeColor: Colors.blue,
onChanged: (T){
print(T);
setState(() {selectedRadio = T;}
);},
),
new Text(
'1st Term'
),
new Radio(
value: 1,
groupValue: selectedRadio,
activeColor: Colors.blue,
onChanged: (T){
print(T);
setState(() {selectedRadio = T;}
);
},
),
new Text(
'2nd Term'
),
new Radio(
value: 2,
groupValue: selectedRadio,
activeColor: Colors.blue,
onChanged: (T){
print(T);
setState(() {selectedRadio = T;}
);
},
),
new Text(
'3rd Term',
),
],
),
Row(
children: <Widget>[
Checkbox(
value: selectedCheck,
activeColor: Colors.blue,
onChanged: (bool value){
print(value);
setState(() {selectedCheck = value;}
);},
),
new Text(
'Present'
),
],
),
],
),
),
);
} ,),
),
);
}
// Widget pupilsCard(BuildContext context, int index){
// final pupil =pupils[index];
// bool selectedRadio = false;
//
// return Container(
// child: Card(
// child: Column(
// children: <Widget>[
// Text(pupil.FirstName+' '+pupil.OtherName+' '+pupil.LastName),
// Text('Select Term'),
// Row(
// children: <Widget>[
//
//
// ],
// ),
// Checkbox(
// value: selectedRadio,
// activeColor: Colors.blue,
// onChanged: (bool value){
// print(value);
// setState(() {selectedRadio = value;}
// );},
// ),
// new Text(
// 'Present'
//
// ),
// ],
// ),
// ),
// );
// }
}
In the onChanged property of your Radio widgets and your Checkbox widget, you are assigning the user selected value to the variable selectedRadio / selectedCheck and here is the problem, because when the new State is created through setState, the ListView is rebuilding and you are reassigning selectedRadio / selectedCheck the old value of the objects in this lines:
int selectedRadio = attendance[index].Term;
bool selectedCheck = attendance[index].attendance;
So you were not changing the actual value of the objects in the List, but you have too:
onChanged: (T) {
print(T);
setState(() => attendance[index].Term = T);
},
and
onChanged: (value) {
print(value);
setState(() => attendance[index].attendance = value);
},