How to use DropdownButton without setting an initial select in flutter? - flutter

Hi I'm new to Flutter.
I had been getting an exception There should be exactly one item with [DropdownButton]'s value: . when using DropdownButton class. And it was resolved once by setting an initial select referring to this Q&A.
But I don't want to set an initial value. Is there any way not to set it but no exception?
Thanks,
P.S.
I have two classes and one constant to show the dropdown button. Here is the constant that is a list for creating DropdownMenuItems:
List<String> prefectures = const [
'北海道',
'青森県',
'岩手県',
'宮城県',
'秋田県',
'山形県',
'福島県',
'茨城県',
'栃木県',
'群馬県',
...
And this is the class that expand DropdownButton.
class MyDropdownButton extends StatelessWidget {
final String value;
final void Function(String?) onChanged;
final List<String> options;
final String? hintText;
const MyDropdownButton(
{required this.value, required this.onChanged, required this.options, this.hintText, super.key}
);
List<DropdownMenuItem<String>> createOptions() {
return options.toSet().map<DropdownMenuItem<String>>(
(option) => DropdownMenuItem(value : option, child : Text(option))
).toList();
}
#override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child : DropdownButton(
elevation : 3,
items : createOptions(),
onChanged : onChanged,
style : const TextStyle(
color : Colors.black,
fontSize : 15
  ),
value : value
)
);
}
}
And this is where I use the class above:
MyDropdownButton(
// They are also passed from other class.
value : widget.prefectureValue, // this has null value
onChanged : widget.onChangedPrefecture, // (String? newValue) => setState(() => _prefectureValue = newValue);
options : prefectures
)

There should be exactly one item with [DropdownButton]'s value: means you are having same value more than one DropdownMenuItem. You can convert the data-list to Set, most time it works.
Here the items is
List<String> items = [...];
String? selectedItem;
And the dropDownButton
DropdownButton<String?>(
items: items
.toSet()
.map(
(e) => DropdownMenuItem<String?>(
value: e,
child: Text(e),
),
)
.toList(),
onChanged: (value) {},
)
Fixed model
class MyDropdownButton extends StatelessWidget {
final String? value;
final void Function(String?) onChanged;
final List<String> options;
final String? hintText;
const MyDropdownButton(
{required this.value,
required this.onChanged,
required this.options,
this.hintText,
super.key});
List<DropdownMenuItem<String>> createOptions() {
return options
.toSet()
.map<DropdownMenuItem<String>>(
(option) => DropdownMenuItem(value: option, child: Text(option)))
.toList();
}
#override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: DropdownButton(
elevation: 3,
items: createOptions(),
onChanged: onChanged,
style: const TextStyle(
color: Colors.black,
fontSize: 15,
),
value: value));
}
}
Here is how I am using it
List<String> prefectures = const [
'北海道',
'青森県',
'岩手県',
'宮城県',
'秋田県',
'山形県',
'福島県',
'茨城県',
'栃木県',
'群馬県',
];
late String? value = prefectures.first;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
MyDropdownButton(
value: value, //null,
onChanged: (v) {
setState(() {
value = v;
});
},
options: prefectures,
),

The solution is simple keep the initialValue null provided that you are on the latest version of flutter 3.5 or above
var _selectedItem;
Widget buildDropdownButton() {
return DropdownButton(
hint: const Text('Select Item'),
value: _selectedItem,
onChanged: (value) {
setState(() {
_selectedItem = value;
});
},
items: ["Item 1", "Item 2", "Item 3", "Item 4"]
.map((e) => DropdownMenuItem(
value: e,
child: Text(e),
))
.toList(),
);
}
the output will look like the following

Related

How can I only check one checkbox at time?

How can I select/check only one checkbox to be checked at time?
And below is my code
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Checkbox(
checkColor: Colors.white,
value: isChecked,
onChanged: (bool value) {
setState(() {
isChecked = value;
// ignore: unnecessary_statements
passData(certId);
});
},
),
],
)),
Option1 - Using a map to maintain the state
Create a map:
final Map<int, bool> _state = {};
then, check if the value for that index is true/false:
return ListView.builder(itemBuilder: (context, index) {
return CheckboxListTile(
value: _state[index] ?? false,
onChanged: (value) {
setState(() {
_state[index] = value!;
});
},
title: Text(_data[index].text),
);
});
Option 2 - using a model:
class CheckBoxModel {
bool isChecked = false;
String text = "";
CheckBoxModel({required this.isChecked, required this.text});
}
and then, generate a List of 30 widgets:
final _data = List.generate(
30, (index) => CheckBoxModel(isChecked: false, text: "Item $index"));
Now, use a ListView.builder and based on the index, to update the corresponding value:
class Testing extends StatefulWidget {
const Testing({Key? key}) : super(key: key);
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
#override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (context, index) {
return CheckboxListTile(
value: _data[index].isChecked,
onChanged: (value) {
setState(() {
_data[index].isChecked = value!;
});
},
title: Text(_data[index].text),
);
});
}
}
See also
Expansion tile trailing icon updates all in list on interaction with one tile. How can I only change the icon for the expanded tile?

How to avoid the user can to write on DropdownMenu in flutter?

I need to avoid the user can to type on the DropdownMenu Widget, but I cannot find how to do that. I was looking for in the api.flutter.dev, But I did not find anything.I would appreciate the help.
class TDSMDropdown extends StatefulWidget {
final List<String> items;
final String? value;
final String? hintText;
final String? label;
final void Function(String?)? onSelected;
final TextEditingController? controller;
const TDSMDropdown({
Key? key,
required this.items,
this.value,
this.hintText,
required this.label,
this.onSelected,
this.controller,
}) : super(key: key);
#override
State<TDSMDropdown> createState() => _TDSMDropdownState();
}
class _TDSMDropdownState extends State<TDSMDropdown> {
// final TextEditingController colorController = TextEditingController();
String? selectedValue;
#override
void initState() {
super.initState();
selectedValue = widget.value;
}
_handleOnSelected(String? newVal) {
widget.onSelected!(newVal);
}
#override
Widget build(BuildContext context) {
return DropdownMenu<String>(
width: MediaQuery.of(context).size.width,
enableSearch: false,
// controller: colorController,
label: widget.label == null ? null : Text(widget.label!),
dropdownMenuEntries: widget.items
.map((e) => DropdownMenuEntry<String>(value: e, label: e))
.toList(),
// inputDecorationTheme: _decoration(),
onSelected: widget.onSelected != null ? _handleOnSelected : null,
);
}
Information
Flutter version 3.7.0
Material 3
dart 2.19.0
Attached the image of the current behavior. Currently I can write on the dropdown input, I do not want that.
I need the same behavior that the DropdownButton2 package has.
you can use DropdownButtonFormField widget for this here is the example
DropdownButtonFormField<int>(
decoration: const InputDecoration(
label: Text("Role"),
),
value: 0,
items: [
const DropdownMenuItem(
alignment: Alignment.center,
value: 0,
enabled: false,
child: Text("Select Your role"),
),
DropdownMenuItem(
value: 1,
child: const Text("Admin"),
onTap: () {
role = "Admin";
},
),
DropdownMenuItem(
value: 2,
child: const Text("Miner"),
onTap: () {
role = "Miner";
},
),
DropdownMenuItem(
value: 3,
child: const Text("Polisher"),
onTap: () {
role = "Polisher";
},
),
DropdownMenuItem(
value: 4,
child: const Text("Certifier"),
onTap: () {
role = "Certifier";
},
),
DropdownMenuItem(
value: 5,
child: const Text("Q/A"),
onTap: () {
role = "Q/A";
},
),
],
onChanged: (value) {
value = value;
},
),
which will give you this result

flutter dynamic list widgets removing not working

in my flutter app I'm using a dynamic form in which the user adds more fields based on their info. this form consists of two textfields and one dropdown. what I want to achieve is as follows.
the issue I'm facing is that when I remove a certain form group it removes from the last index but the value of the form is removed correctly. but the value from the UI removed is the last one. for the textfields I can create controller and manage with the through dispose method. but how can I make it work for the dropdown as well?
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
class Purchased extends StatefulWidget {
const Purchased({Key? key}) : super(key: key);
#override
State<Purchased> createState() => _PurchasedState();
}
class _PurchasedState extends State<Purchased> {
List<UserInfo> list = [];
List<TextEditingController> textControllerList = [];
List<TextEditingController> textControllerList1 = [];
Map<String, String> listCtrl = {};
#override
void dispose() {
textControllerList.forEach((element) {
element.dispose();
});
textControllerList1.forEach((element) {
element.dispose();
});
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
/// every time you add new Userinfo, it will generate new FORM in the UI
list.add(UserInfo());
setState(() {}); // dont forget to call setState to update UI
},
child: const Icon(Icons.add),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: ((context, index) {
return Column(
children: [
Text('phone'),
Text(list[index].phone),
Text('email'),
Text(list[index].email),
Text('category'),
Text(list[index].category)
],
);
})),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: ((context, index) {
TextEditingController controller = TextEditingController();
TextEditingController controller1 = TextEditingController();
textControllerList.add(controller);
textControllerList1.add(controller1);
return MyForm(
// dont forget use the key, to make sure every MyForm is has identity. to avoid missed build
textEditingController: textControllerList[index],
textEditingController1: textControllerList1[index],
key: ValueKey(index),
//pass init value so the widget always update with current value
initInfo: list[index],
dataCtrl: listCtrl,
// every changes here will update your current list value
onChangePhone: (phoneVal) {
if (phoneVal != null) {
list[index].setPhone(phoneVal);
setState(() {});
}
},
// every changes here will update your current list value
onchangeEmail: (emailVal) {
if (emailVal != null) {
list[index].setEmail(emailVal);
setState(() {});
}
},
onchangeCategory: (categoryVal) {
if (categoryVal != null) {
list[index].setCategory(categoryVal);
setState(() {});
}
},
onremove: () {
list.removeAt(index);
textControllerList.removeAt(index);
textControllerList1.removeAt(index);
if (listCtrl.containsKey(index)) {
listCtrl.remove(index);
}
setState(() {});
});
})),
)
],
),
);
}
}
class MyForm extends StatefulWidget {
final UserInfo initInfo;
final Function(String?) onChangePhone;
final Function(String?) onchangeEmail;
final Function(String?) onchangeCategory;
final TextEditingController textEditingController;
final TextEditingController textEditingController1;
Map<String, String> dataCtrl = {};
final VoidCallback? onremove;
MyForm({
key,
required this.initInfo,
required this.onChangePhone,
required this.onchangeEmail,
required this.onchangeCategory,
required dataCtrl,
required this.onremove,
required this.textEditingController,
required this.textEditingController1,
});
#override
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
TextEditingController _phoneCtrl = TextEditingController();
TextEditingController _emailCtrl = TextEditingController();
String? selected;
final List<String> category = [
'Manager',
'Reception',
'Sales',
'Service',
];
#override
void initState() {
super.initState();
// set init value
_phoneCtrl = TextEditingController(text: widget.initInfo.phone);
_emailCtrl = TextEditingController(text: widget.initInfo.email);
selected = widget.initInfo.category;
}
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
child: Column(
children: [
IconButton(onPressed: widget.onremove, icon: Icon(Icons.remove)),
TextFormField(
controller: widget.textEditingController,
onChanged: widget.onChangePhone,
),
TextFormField(
controller: widget.textEditingController1,
onChanged: widget.onchangeEmail,
),
DropdownButtonFormField2(
//key: _key,
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
),
),
isExpanded: true,
hint: const Text(
'Select Category',
style: TextStyle(fontSize: 14),
),
icon: const Icon(
Icons.arrow_drop_down,
color: Colors.black45,
),
iconSize: 30,
buttonHeight: 60,
//value: category[1],
buttonPadding: const EdgeInsets.only(left: 20, right: 10),
items: category
.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
))
.toList(),
validator: (value) {
if (value == null) {
return 'Please select Catagory.';
}
},
onChanged: widget.onchangeCategory,
onSaved: (value) {
widget.onchangeCategory;
if (widget.dataCtrl.containsKey('${widget.key}') &&
value != null) {
widget.dataCtrl['${widget.key}'] = value.toString();
}
})
/// same like TextFormField, you can create new widget below
/// for dropdown, you have to 2 required value
/// the initValue and the onchage function
],
),
);
}
}
class UserInfo {
///define
String _phone = '';
String _email = '';
String _category = '';
/// getter
String get phone => _phone;
String get email => _email;
String get category => _category;
///setter
void setPhone(String phone) {
_phone = phone;
}
void setEmail(String email) {
_email = email;
}
void setCategory(String category) {
_category = category;
}
}
screenshot of the problem. check the category list from the text above vs the dropdown value.
Your code is a bit confusing, i tried to reproduce your error and something came to my attention:
The problem seems to be with the dropdown, as the text fields reset correctly.
You initialize your data in the initState in MyForm, however when you change a category for example, this is no longer called. So I moved that out to the build method. Also I have passed a value to the dropdown. This made it work for me:
Widget build(BuildContext context) {
_phoneCtrl = TextEditingController(text: widget.initInfo.phone);
_emailCtrl = TextEditingController(text: widget.initInfo.email);
selected = widget.initInfo.category;
Add this to your DropdownButton
value: selected!.isEmpty ? null : selected,

Why is the value of the drop-down list (button) not assigned?

I have about 7 DropDownButton, so I created a class in which I just change the values. Here is my class -
class DropDown extends StatelessWidget {
late List<DropdownMenuItem<dynamic>> menuItem;
String? menuValue;
String? hintValue;
DropDown(this.menuItem, this.menuValue, this.hintValue);
#override
Widget build(BuildContext context) {
return StatefulBuilder(builder: (BuildContext context, StateSetter mystate){
return Container(
child: DropdownButton(
underline: Container(
height: 0.1,
color: Colors.transparent,
),
hint: Text('$hintValue'),
value: menuValue,
icon: const Icon(Icons.keyboard_arrow_down),
items: menuItem,
onChanged: (dynamic newValue) {
mystate(() {
menuValue = newValue!;
});
}
)
)
);
}) ;
}
}
Below is how I display these dropdowns -
DropDown(LoginClass.typeModemsDropDown, LoginClass.typeModemsValue, 'Выберите тип модема'),
const SizedBox(
height: 12,
),
DropDown(LoginClass.vendorCountersDropDown, LoginClass.vendorCountersValue, 'text'),
When you try to display in the console LoginClass.vendorCountersValue - the value of null. However, if at this point, I will output print(menuValue); -
mystate(() {
menuValue = newValue!;
print(menuValue);
});
Then, it displays the correct values ​​in the console for me
data - Here is how I am getting the data -
LoginClass.viewModemsDropDown .add(DropdownMenuItem<dynamic>(value: resp['data'][i]['id'].toString(), child: Text(resp['data'][i]['attributes'] ['view_name']))),
// LoginClass.viewModemsDropDown - ["blue": 1, "red": 2] !VALUE!
Tell me, how can I see the values of the selected item anywhere in the code?
Can you point out my mistake and tell me what I'm doing wrong?
You can use a callback method on your DropDown. Also use named constructor cases like this.
class DropDown extends StatelessWidget {
final Function(dynamic) callback;
final List<DropdownMenuItem<dynamic>> menuItem;
final String? menuValue;
final String? hintValue;
const DropDown({
Key? key,
required this.callback,
required this.menuItem,
required this.menuValue,
required this.hintValue,
}) : super(key: key);
#override
Widget build(BuildContext context) {
String? currentValue = menuValue;
return StatefulBuilder(
builder: (BuildContext context, StateSetter mystate) {
return Container(
child: DropdownButton(
underline: Container(
height: 0.1,
color: Colors.transparent,
),
hint: Text('$hintValue'),
value: menuValue,
icon: const Icon(Icons.keyboard_arrow_down),
items: menuItem,
onChanged: (dynamic newValue) {
mystate(() {
currentValue = newValue!;
});
callback(currentValue);
},
),
);
});
}
}
And use case
DropDown(
menuItem: ["a", "b"]
.map(
(e) => DropdownMenuItem(
child: Text(e),
value: e,
),
)
.toList(),
callback: (p0) {
print(p0);
},
hintValue: "Set",
menuValue: "a",
),

Flutter Modalbottomsheet with ExpansionPanelList and FilterChip

I want create a filter for my app. I use ModalBottom. The filters are multiple so I use ExpansionPanelList for this. When I open a Expansion I have the list of FilterChip Item.
I have 2 problems :
When I click in a FilterChip the background not change for see the selection. But my list tags and collections have the correct value.
When I close ModalBottomSheet and I reopen this : the filters don't save old value.
Thanks,
//in other file : the call of the filters
void _showFilter() async {
/*data = {"tags":["tag1", "tag2","tag3" ...],
"collection": ["coll1","coll2", ...]}*/
var data = await repository().filter();
Map<String, dynamic> filters = jsonDecode(data);
await showModalBottomSheet(
context: context,
builder: (context) =>
Filter(title: l10n.filterText, filters: filters));
setState(() {});
}
//my file filter
class Filter extends StatefulWidget {
final String title;
final Map<String, dynamic> filters;
const Filter({super.key, required this.title, required this.filters});
#override
State<Filter> createState() => FilterState();
}
class FilterState extends State<Filter> {
late Map<String, dynamic> filters;
static late List<ItemExpansionPanel> _data;
late List<dynamic> tags;
var selectedTags = [];
late List<dynamic> collections;
var selectedCollections = [];
#override
void initState() {
filters = widget.filters;
tags = filters["tags"]
.map((option) => {"key": option, "title": option, "value": false})
.toList();
collections = filters["collection"]
.map((option) => {"key": option, "title": option, "value": false})
.toList();
super.initState();
}
#override
void didChangeDependencies() {
_data = generateItems(_buildPanelList());
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8),
child: SingleChildScrollView(
child: Column(
children: [
Text(widget.title,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
ExpansionPanelList(
dividerColor: Theme.of(context).primaryColor,
elevation: 2,
expandedHeaderPadding: const EdgeInsets.all(0),
expansionCallback: (int item, bool status) {
setState(() => _data[item].isExpanded = !status);
},
children: _data.map<ExpansionPanel>((ItemExpansionPanel item) {
return ExpansionPanel(
canTapOnHeader: true,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(title: Text(item.headerValue));
},
body: item.body,
isExpanded: item.isExpanded,
);
}).toList(),
),
],
),
),
);
}
List<Map<String, dynamic>> _buildPanelList() {
return filters.keys
.map((key) =>
{"title": key, "expandedValue": key, "body": _generateItems(key)})
.toList();
}
Widget _generateItems(String key) {
List<dynamic> list = [];
switch (key) {
case "tags":
list = tags;
break;
case "collection":
list = collections;
break;
default:
break;
}
List<Widget> itemFilters =
list.asMap().entries.map((item) => _buildChip(item, key)).toList();
return Wrap(
alignment: WrapAlignment.start,
runAlignment: WrapAlignment.start,
runSpacing: 8,
spacing: 16,
children: itemFilters);
}
Widget _buildChip(MapEntry<int, dynamic> map, String key) {
final foregroundColor = map.value['value'] ? Colors.white : Colors.black;
return FilterChip(
selected: selectedTags.contains(map.value['title']),
selectedColor: Theme.of(context).primaryColor,
disabledColor: null,
shape: StadiumBorder(
side: BorderSide(color: Theme.of(context).primaryColor)),
labelStyle:
TextStyle(color: foregroundColor, fontWeight: FontWeight.bold),
label: Text(map.value['title']),
onSelected: (bool value) {
!selectedTags.contains(map.value['title'])
? selectedTags.add(map.value['title'])
: selectedTags.remove(map.value['title']);
setState(() => _itemListMaj(key, map.key));
},
);
}
void _itemListMaj(String key, int index) {
switch (key) {
case "tags":
bool curValue = tags[index]['value'] as bool;
tags[index].update('value', (value) => !curValue);
break;
case "collection":
bool curValue = collections[index]['value'] as bool;
collections[index].update('value', (value) => !curValue);
break;
default:
break;
}
}
}
I resolved the first problem. In fact, didChangeDependencies() is'nt call in setState. So I add the construct panel in the setState.
setState(() {
_itemListMaj(key, map.key);
_data = generateItems(_buildPanelList());
});
I have always the second problem to resolve :
When I close ModalBottomSheet and I reopen this : the filters don't save old value.
How keep safe the value of my list tags and collections between my page and the modalbottomsheet ?
Thanks,