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: () {},
),
),
),
),
),
],
),
),
));
}
}
Related
I have a radio buttons created but while on click of the buttons the value is not getting changed.
here is my code.
Widget _buildOmsIDTextField() {
// Group Value for Radio Button.
int id = 1;
List<OptionList> nList = [
OptionList(
padding : const EdgeInsets.fromLTRB(14.0, 180, 25, 50),
index: 1,
option: "Accept",
),
OptionList(
padding : const EdgeInsets.all(14.0),
index: 2,
option: "Reject",
),
];
return Column(
children: <Widget>[
const Padding(
padding : EdgeInsets.all(14.0),
child: Text("Please Select an Option", style: TextStyle(fontSize: 23))
),
SizedBox(
height: 150.0,
child: Column(
children:
nList.map((data) => RadioListTile(
title: Text(data.option),
groupValue: data.index,
value: id,
onChanged: (val) {
setState(() {
radioItemHolder = data.option ;
id = data.index;
});
},
)).toList(),
),
),
],
);
}
Any help would be highly appreciated!
While you are calling _buildOmsIDTextField() inside build method, the int id = 1; will always get 1. Put your group value outside the build method. And use same group value on radio buttons.
// Group Value for Radio Button. I also prefer putting any decleared varable for radio button here
int id = 1;
Widget _buildOmsIDTextField() {...
...
nList.map((data) => RadioListTile(
title: Text(data.option),
groupValue: id,
value: data.option,
}
#override
Widget build(BuildContext context) {
A better way of doing this, providing data type on RadioListTile.
OptionList? selectOption;
List<OptionList> nList = [
OptionList(
padding: const EdgeInsets.fromLTRB(14.0, 180, 25, 50),
index: 1,
option: "Accept",
),
OptionList(
padding: const EdgeInsets.all(14.0),
index: 2,
option: "Reject",
),
];
Widget _buildOmsIDTextField() {
return Column(
children: <Widget>[
const Padding(
padding: EdgeInsets.all(14.0),
child: Text("Please Select an Option",
style: TextStyle(fontSize: 23))),
SizedBox(
height: 150.0,
child: Column(
children: nList
.map((data) => RadioListTile<OptionList>(
title: Text(data.option),
groupValue: selectOption,
value: data,
onChanged: (val) {
setState(() {
selectOption = val;
});
},
))
.toList(),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildOmsIDTextField(),
);
}
I am using DropdownButton and I am facing the following issue. I'm using a checkbox in elements, but when I click on an element, I don't get a checkmark indicating that the checkbox has been clicked. As a result, I need to close and reopen it, and then I will see the changes that were clicked on the "checkbox". The second problem is that when I select one element, all elements are selected for me. As a final result, I need to get so that I can select an element and the checkbox is immediately marked, if 2 elements are needed, then two, and so on. Tell me how to fix these problems, I will be grateful for the help?
dropdown
class DropdownWidget extends StatefulWidget {
List<String> items;
SvgPicture? icon;
double width;
DropdownWidget({
Key? key,
required this.items,
required this.icon,
required this.width,
}) : super(key: key);
#override
State<DropdownWidget> createState() => _DropdownWidgetState();
}
class _DropdownWidgetState extends State<DropdownWidget> {
String? selectedValue;
bool isChecked = false;
#override
void initState() {
super.initState();
if (widget.items.isNotEmpty) {
selectedValue = widget.items[1];
}
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
child: DropdownButtonHideUnderline(
child: DropdownButton2(
items: widget.items
.map((item) => DropdownMenuItem<String>(
value: item,
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: constants.Colors.white.withOpacity(0.1),
width: 1,
),
),
),
child: Center(
child: Row(
children: [
if (item == selectedValue)
const SizedBox(
width: 0,
),
Expanded(
child: Text(
item,
style: constants.Styles.smallTextStyleWhite,
),
),
Checkbox(
checkColor: Colors.black,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value!;
});
},
),
],
),
),
),
))
.toList(),
value: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value as String;
});
},
icon: SvgPicture.asset(constants.Assets.arrowDropdown),
iconSize: 21,
buttonHeight: 27,
itemHeight: 47,
dropdownMaxHeight: 191,
dropdownWidth: 140,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: constants.Colors.purpleMain,
),
color: constants.Colors.greyDark,
),
selectedItemBuilder: (context) {
return widget.items.map(
(item) {
return Row(
children: [
widget.icon ?? const SizedBox(),
const SizedBox(width: 8),
Text(
item,
style: constants.Styles.bigBookTextStyleWhite,
),
],
);
},
).toList();
},
),
),
);
}
}
items
final List<String> items = const [
"All EV's",
'Main EV',
'<EV2>',
];
I hope this example explains the concept. For simplcity I made simple a new file, run it and see the results:
Then main idea in two lists, _checkList contain values of the CheckBox and _selectedList handles the main dropdown widget to show the selection.
Feel free to ask any questions and I'm happy to help
import 'package:flutter/material.dart';
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const AnimationDemo(number: 5);
}
}
class AnimationDemo extends StatefulWidget {
const AnimationDemo({Key? key, this.number = 2}) : super(key: key);
final int number;
#override
State<AnimationDemo> createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo> {
late List<bool> _checkList;
late List<int> _selectedIndex;
bool _isOpen = false;
#override
void initState() {
_checkList = List.filled(widget.number, false);
_selectedIndex = <int>[];
super.initState();
}
List<DropDownItem> generateItems() {
var tmp = <DropDownItem>[];
for (var i = 0; i < _checkList.length; i++) {
tmp.add(DropDownItem(
isChecked: _checkList[i],
onChanged: (value) {
setState(() {
_checkList[i] = value!;
if (value && !_selectedIndex.contains(i)) {
_selectedIndex.add(i);
} else {
_selectedIndex.remove(i);
}
});
},
));
}
return tmp;
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text((_selectedIndex.isEmpty)
? 'Nothing Selected'
: _selectedIndex.join(',')),
),
GestureDetector(
onTap: () {
setState(() {
_isOpen = !_isOpen;
});
},
child: const Icon(Icons.arrow_downward),
),
],
),
AnimatedOpacity(
opacity: (_isOpen) ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Column(
mainAxisSize: MainAxisSize.min,
children: generateItems(),
),
)
],
),
);
}
}
class DropDownItem extends StatelessWidget {
final bool isChecked;
final Function(bool?)? onChanged;
const DropDownItem({Key? key, this.onChanged, this.isChecked = false})
: super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
const Expanded(child: Text('Demo item')),
Checkbox(value: isChecked, onChanged: onChanged)
],
);
}
}
Here's how to achieve the Multiselect dropdown with DropdownButton2:
final List<String> items = [
'Item1',
'Item2',
'Item3',
'Item4',
];
List<String> selectedItems = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButtonHideUnderline(
child: DropdownButton2(
isExpanded: true,
hint: Align(
alignment: AlignmentDirectional.center,
child: Text(
'Select Items',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
),
items: items.map((item) {
return DropdownMenuItem<String>(
value: item,
//disable default onTap to avoid closing menu when selecting an item
enabled: false,
child: StatefulBuilder(
builder: (context, menuSetState) {
final _isSelected = selectedItems.contains(item);
return InkWell(
onTap: () {
_isSelected
? selectedItems.remove(item)
: selectedItems.add(item);
//This rebuilds the StatefulWidget to update the button's text
setState(() {});
//This rebuilds the dropdownMenu Widget to update the check mark
menuSetState(() {});
},
child: Container(
height: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
_isSelected
? const Icon(Icons.check_box_outlined)
: const Icon(Icons.check_box_outline_blank),
const SizedBox(width: 16),
Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
],
),
),
);
},
),
);
}).toList(),
//Use last selected item as the current value so if we've limited menu height, it scroll to last item.
value: selectedItems.isEmpty ? null : selectedItems.last,
onChanged: (value) {},
buttonHeight: 40,
buttonWidth: 140,
itemHeight: 40,
itemPadding: EdgeInsets.zero,
selectedItemBuilder: (context) {
return items.map(
(item) {
return Container(
alignment: AlignmentDirectional.center,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
selectedItems.join(', '),
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
);
},
).toList();
},
),
),
),
);
}
Also, I've added it as an example to the package doc "Example 4" so you can get back to it later.
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(),
),
Need to access a single value when the item is selected in the list.
When the item is clicked, it prints the whole list of objects, instead the value of the selected object.
For example, when user selects Spanish, should print SP instead of the list of objects.
I have a class and a list for the languages, but for this question is not relevant.
This is what I have achieved so far:
List <Object> Dictionary = [
{'Spanish': 'SP'},
{'Italian': 'IT'},
{'German': 'DE'},
{'Arab': 'AR'},
{'Greek': 'GR'},
{'Thai': 'TH'},
{'Chinese': 'CH'},
{'French': 'FR'}
];
List<Language> newDataList;
final finalList = dictionary.values.toList();
onItemChanged(String value) {
setState(() {
newDataList = languages.where((lang) => lang.title.toLowerCase().contains(value.toLowerCase())).toList();
});
}
#override
initState() {
super.initState();
newDataList = [...languages];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: _textController,
decoration: InputDecoration(
icon: Icon(Icons.search),
hintText: 'Type languages',
),
onChanged: onItemChanged,
),
),
Expanded(
child: ListView(
padding: EdgeInsets.all(12.0),
children: newDataList.map((data) {
return Ink(
color: data.selected ? TheBaseColors.lightGreen : Colors.transparent,
child: ListTile(
title: Text(data.title),
onTap: () {
setState(() {
data.selected = !data.selected;
print("${data.title} is now ${data.selected ? "selected" : "not selected"}");
print(Dictionary);
});
}),
);
}).toList(),
),
),
Expanded(
child: Column(children: [
Text('Selected languages :'),
Expanded(
child: ListView(
children: newDataList.where((l) => l.selected).map((l) => Padding(child: Text(l.title), padding: EdgeInsets.only(right: 10))).toList()))
]))
],
),
);
}
}
Tried dictionary.value, dictionary[value]
Instead of List<Object> you want to use Map<String, String> for this.
Map<String, String> dictionary = {
'Spanish': 'SP',
'Italian': 'IT',
'German': 'DE',
'Arab': 'AR',
'Greek': 'GR',
'Thai': 'TH',
'Chinese': 'CH',
'French': 'FR'
};
If you use this you can easily do dictionary['Spanish'] and receive 'SP' as value.
I have a dropdown button which works fine, but when I try to set a default value it will fail with the following error:
'package:flutter/src/material/dropdown.dart': Failed assertion: line 620 pos 15: 'items == null || items.isEmpty || value == null || items.where((DropdownMenuItem item) => item.value == value).length == 1': is not true.
This is my dropdown button:
Widget changeWorkspace() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Padding(
padding: EdgeInsets.all(8.0),
child: DropdownButton<AssignedWorkspace>(
isExpanded: true,
hint: Text("SELECT WORKSPACE"),
value: selectedWorkspace,
onChanged: (dropdownValueSelected) {
setState(() {
selectedWorkspace = dropdownValueSelected;
});
},
items: workspaces != null && workspaces.length > 0
? workspaces.map((AssignedWorkspace workspace) {
return new DropdownMenuItem<AssignedWorkspace>(
value: workspace,
child: new Text(workspace.name,
style: new TextStyle(color: Colors.black)),
);
}).toList()
: null),
),
]);
});
}
I've tried to set the value of selectedWorkspace onInit as follows but it fails.
selectedWorkspace = new AssignedWorkspace(
id: userSettings.currentWorkspaceId,
name: userSettings.currentWorkspaceName);
Is there a way of setting a default value in a dropdown button?
import 'package:flutter/material.dart';
import '../config/app_theme.dart';
class DropdownWidget extends StatefulWidget {
final String title;
final List<String> items;
final ValueChanged<String> itemCallBack;
final String currentItem;
final String hintText;
DropdownWidget({
this.title,
this.items,
this.itemCallBack,
this.currentItem,
this.hintText,
});
#override
State<StatefulWidget> createState() => _DropdownState(currentItem);
}
class _DropdownState extends State<DropdownWidget> {
List<DropdownMenuItem<String>> dropDownItems = [];
String currentItem;
AppTheme appTheme;
_DropdownState(this.currentItem);
#override
void initState() {
super.initState();
for (String item in widget.items) {
dropDownItems.add(DropdownMenuItem(
value: item,
child: Text(
item,
style: TextStyle(
fontSize: 16,
),
),
));
}
}
#override
void didUpdateWidget(DropdownWidget oldWidget) {
if (this.currentItem != widget.currentItem) {
setState(() {
this.currentItem = widget.currentItem;
});
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
appTheme = AppTheme(Theme.of(context).brightness);
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 6),
child: Text(
widget.title,
style: appTheme.activityAddPageTextStyle,
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 3, horizontal: 15),
margin: EdgeInsets.only(top: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Colors.white,
boxShadow: [
BoxShadow(
offset: Offset(0, 2),
blurRadius: 10,
color: Color(0x19000000),
),
],
),
child: DropdownButtonHideUnderline(
child: DropdownButton(
icon: appTheme.activityAddPageDownArrowSVG,
value: currentItem,
isExpanded: true,
items: dropDownItems,
onChanged: (selectedItem) => setState(() {
currentItem = selectedItem;
widget.itemCallBack(currentItem);
}),
hint: Container(
child: Text(widget.hintText, style: appTheme.hintStyle),
),
),
),
),
],
),
);
}
}
This is my dropDownWidget without optimization. It has currentItem. You could use it like:
DropdownWidget(
title: kStatus,
items: state.customerStepInfo.statusList,
currentItem: status,
hintText: kCommonPick,
itemCallBack: (String status) {
this.status = status;
},
)
You need implement "equals" in class AssignedWorkspace. I used equatable package.
Example class AssignedWorkspace
class AssignedWorkspace extends Equatable {
final String id;
final String name;
AssignedWorkspace(this.id, this.name);
#override
List<Object> get props => [id];
}
For me id of one of the element is null, once added id is made non-null issue got fixed.
I changed the value of the dropdown var to 1 initially
var _value = '1';
So when the dropdown button has to display its value it displays the one whose value I have set 1 as in the items list in DropDownButton
DropdownButton(
underline: Container(),
onChanged: (value) {
setState(() {
_value = value;
});
},
value: _value,
items: [
DropdownMenuItem(
value: "1",
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(MaterialCommunityIcons.devices),
SizedBox(width: 10),
Text(
"Consumption",
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.w600),
),
],
),
),
DropdownMenuItem(
value: "2",
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(MaterialCommunityIcons.solar_panel),
SizedBox(width: 10),
Text(
"Generation",
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.w600),
),
],
),
),
],
),
if you want to see only an initial value you can use hint text named parameter of drop down button and set a text widget. i dont know whether it is a good practice or not.