How to avoid the user can to write on DropdownMenu in flutter? - 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

Related

How to use DropdownButton without setting an initial select in 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

in dynamic form list removing index not working

I've recently asked a question on how to create a group of form dynamically. and i've got an answer. but the problem was when removed an index of the group it removes the last added form. but the value is correct.
for example if i add 3 dynamic group formfields and removed the second index index[1] the ui update will remove the last index but the removed value is only the selected index. why is the ui not working as expected?
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 = [];
#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) {
return MyForm(
// dont forget use the key, to make sure every MyForm is has identity. to avoid missed build
key: ValueKey(index),
//pass init value so the widget always update with current value
initInfo: list[index],
// 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);
setState(() {});
});
})),
)
],
),
);
}
}
class MyForm extends StatefulWidget {
final UserInfo initInfo;
final Function(String?) onChangePhone;
final Function(String?) onchangeEmail;
final Function(String?) onchangeCategory;
final VoidCallback? onremove;
const MyForm({
key,
required this.initInfo,
required this.onChangePhone,
required this.onchangeEmail,
required this.onchangeCategory,
required this.onremove,
});
#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: _phoneCtrl,
onChanged: widget.onChangePhone,
),
TextFormField(
controller: _emailCtrl,
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,
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: widget.onchangeCategory)
/// 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;
}
}
any help is appreciated.

TextField widgets lose focus (Flutter)

I am creating an app with Flutter TextField widgets:
class CategoryData {
int? id;
String name;
String description;
CategoryData({this.id, required this.name, required this.description});
}
class CategoriesEdit extends StatefulWidget {
Database? db;
CategoryData? category;
CategoriesEdit({super.key, required this.db, required this.category});
#override
State<StatefulWidget> createState() => CategoriesEditState();
}
class CategoriesEditState extends State<CategoriesEdit> {
CategoryData? category;
void saveState(BuildContext context) {
// ...
}
#override
Widget build(BuildContext context) {
if (category == null) {
setState(() {
category = widget.category ?? CategoryData(name: "", description: "");
});
}
return Scaffold(
appBar: AppBar(
leading: InkWell(
child: const Icon(Icons.arrow_circle_left),
onTap: () => Navigator.pop(context)),
title: const Text("Edit Category"),
),
body: Column(children: [
Column(key: const Key('name'), children: [
const Text("Category name:*"),
TextField(
controller: TextEditingController(text: category!.name),
onChanged: (value) {
setState(() {
category!.name = value;
});
})
]),
Column(key: const Key('description'), children: [
const Text("Description:"),
TextField(
controller: TextEditingController(text: category!.description),
onChanged: (value) {
setState(() {
category!.description = value;
});
})
]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
ElevatedButton(
onPressed: () => saveState(context), // passing false
child: const Text('OK'),
),
OutlinedButton(
onPressed: () => Navigator.pop(context, false),
// passing false
child: const Text('Cancel'),
),
]),
]));
}
}
But after I type a character in one of these two widgets, the cursor moves before the first character and the Android keyboard widget disappears. Why? And how to fix that bug?
I tried adding widget keys, but as you see it didn't help.
There is a lot of things going wrong here, not only the stuff mentioned in the other answer.
Move the setState in the builder into initState:
if (category == null) {
setState(() {
category = widget.category ?? CategoryData(name: "", description: "");
});
}
Don't use setState in the onChanged callback. Change:
onChanged: (value) {
setState(() {
category!.description = value;
});
}
to this:
onChanged: (value) {
category!.description = value;
}
Store the TextEditingControllers, because you have to dispose them once we dispose the state.
If you are already using TextEditingControllers, then you don't need the onChanged callback. Just take text from the controller like explained in the other answer.
You do not have to do
controller: TextEditingController(text: category!.name)
because the controller's text automatically changes once you connect it to TextField.
The reason is once you set some text to the controller, it re-applies the text thus moving the cursor to the front.
I have solved this for you :
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CategoryData {
int? id;
String name;
String description;
CategoryData({this.id, required this.name, required this.description});
}
class CategoriesEdit extends StatefulWidget {
CategoryData? category;
CategoriesEdit({required this.category});
#override
State<StatefulWidget> createState() => CategoriesEditState();
}
class CategoriesEditState extends State<CategoriesEdit> {
CategoryData? category;
// Database? db;
TextEditingController nametextController = TextEditingController();
TextEditingController descriptionTextController = TextEditingController();
void saveState(BuildContext context) {
// ...
}
#override
Widget build(BuildContext context) {
if (category == null) {
setState(() {
category = widget.category ?? CategoryData(name: "", description: "");
});
}
nametextController.text = category!.name??"";
descriptionTextController.text = category!.description??"";
return Scaffold(
appBar: AppBar(
leading: InkWell(
child: const Icon(Icons.arrow_circle_left),
onTap: () => Navigator.pop(context)),
title: const Text("Edit Category"),
),
body: Column(children: [
Column(key: const Key('name'), children: [
const Text("Category name:*"),
TextField(
controller: nametextController,
onChanged: (value) {
setState(() {
category!.name = value;
});
})
]),
Column(key: const Key('description'), children: [
const Text("Description:"),
TextField(
controller: descriptionTextController,
onChanged: (value) {
setState(() {
category!.description = value;
});
})
]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
ElevatedButton(
onPressed: () => saveState(context), // passing false
child: const Text('OK'),
),
OutlinedButton(
onPressed: () => Navigator.pop(context, false),
// passing false
child: const Text('Cancel'),
),
]),
]));
}
}
I have tested this code and it is working fine, let me know if you have any doubt. Hope this helps you.

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: A value of type 'GameNameEnum' can't be assigned to a variable of type 'GameNameEnum'

I am getting a really strange error in my code. When I try to use _dialogPlatform = newValue; or _dialogGameName = newValue; I get a red highlight by VS code saying I cannot assign a value with some type to a variable of the same type.
Complete error: A value of type 'Platform' can't be assigned to a variable of type 'Platform'.
Try changing the type of the variable, or casting the right-hand type to 'Platform'.
Please help.
import 'package:all_gta_cheats/enums/enums.dart';
import 'package:all_gta_cheats/widgets/drop_down_menu.dart';
import 'package:flutter/material.dart';
class FilterDialogBox extends StatefulWidget {
const FilterDialogBox({
Key? key,
required this.initialPlatformValue,
required this.initialGameNameValue,
required this.initialIsFavValue,
required this.onApply,
}) : super(key: key);
final Platform initialPlatformValue;
final GameNameEnum initialGameNameValue;
final bool initialIsFavValue;
final Function(
Platform platform,
GameNameEnum gameNameEnum,
bool isFav,
) onApply;
#override
_FilterDialogBoxState createState() => _FilterDialogBoxState();
}
class _FilterDialogBoxState extends State<FilterDialogBox> {
final TextStyle actionsTextStyle = TextStyle(fontSize: 18);
late GameNameEnum _dialogGameName;
late Platform _dialogPlatform;
late bool _dialogIsFav;
#override
void initState() {
super.initState();
_dialogGameName = widget.initialGameNameValue;
_dialogPlatform = widget.initialPlatformValue;
_dialogIsFav = widget.initialIsFavValue;
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Search Filter'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
DropDownMenu<Platform>(
initialValue: _dialogPlatform,
itemsList: Platform.values,
onChanged: <Platform>(newValue) {
//Platform value changes here
_dialogPlatform = newValue;
print(newValue.toString());
},
),
DropDownMenu<GameNameEnum>(
initialValue: _dialogGameName,
itemsList: GameNameEnum.values,
onChanged: <GameNameEnum>(newValue) {
//GameName value changes here
_dialogGameName = newValue;
print(newValue.toString());
},
),
SwitchListTile(
title: const Text('Show only favourites'),
value: _dialogIsFav,
onChanged: (bool value) {
setState(() {
_dialogIsFav = value;
print(_dialogIsFav);
//widget.dialogIsFav = value;
});
},
secondary: const Icon(Icons.favorite),
),
],
),
),
actions: <Widget>[
TextButton(
child: Text(
'Cancel',
style: actionsTextStyle,
),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(
'Apply',
style: actionsTextStyle,
),
onPressed: () {
widget.onApply(_dialogPlatform, _dialogGameName, _dialogIsFav);
Navigator.of(context).pop();
},
),
],
);
}
}