I try to create a dropdown and to populate it with few objects which represents few servers from where the user can pick one, but when I run the app I'm getting an error saying:
The following assertion was thrown building DropdownWidget(dirty, state: _DropdownWidgetState#1f58f): There should be exactly one item with [DropdownButton]'s value: Instance of 'ServerModel'. Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
Can you please help me to identify what I'm doing wrong in my code?
import 'package:flutter/material.dart';
class ServerSettingsPage extends StatefulWidget {
#override
_ServerSettingsPageState createState() => _ServerSettingsPageState();
}
class _ServerSettingsPageState extends State<ServerSettingsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Server Settings")),
body: _buildUI(),
);
}
Widget _buildUI() {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 20, 0, 0),
child: Center(
child: Column(
children: <Widget>[
Text(
'Select a server:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
DropdownWidget(),
],
),
),
);
}
}
class DropdownWidget extends StatefulWidget {
DropdownWidget({Key key}) : super(key: key);
#override
_DropdownWidgetState createState() => _DropdownWidgetState();
}
class _DropdownWidgetState extends State<DropdownWidget> {
ServerModel dropdownValue =
ServerModel(name: 'Default', url: 'https://defaultServer.com/');
#override
Widget build(BuildContext context) {
return DropdownButton<ServerModel>(
value: dropdownValue,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: Colors.purple[700]),
underline: Container(
height: 2,
color: Colors.purple[700],
),
onChanged: (ServerModel newServer) {
setState(() {
dropdownValue = newServer;
});
},
items: <ServerModel>[
ServerModel(name: 'Default', url: 'https:defaultServer.com/'),
ServerModel(name: 'Alpha', url: 'https://alphaServer.com/'),
ServerModel(name: 'Beta', url: 'https://betaServer.com/'),
].map<DropdownMenuItem<ServerModel>>((ServerModel server) {
return DropdownMenuItem<ServerModel>(
value: server,
child: Text(server.name, style: TextStyle(fontSize: 20)),
);
}).toList(),
);
}
}
And here is the ServerModel class:
class ServerModel {
ServerModel({this.name, this.url});
ServerModel.empty() {
this.name = null;
this.url = null;
}
String name;
String url;
}
Many thanks for reading this post.
There should be exactly one item with [DropdownButton]'s value:
Instance of 'ServerModel'. Either zero or 2 or more
[DropdownMenuItem]s were detected with the same value
This is happening because selected value inside the dropdown has to point to an existing list item (and obviously there shouldn't be any duplicates in that list). The way you've set it up right now is that the list of ServerModel is being generated during your widget build time and once it is built there no reference to the list inside the state of the widget.
I hope my answer is clear enough, also take a look at correct code bellow:
class _DropdownWidgetState extends State<DropdownWidget> {
List<ServerModel> serverModels = <ServerModel>[
ServerModel(name: 'Default', url: 'https:defaultServer.com/'),
ServerModel(name: 'Alpha', url: 'https://alphaServer.com/'),
ServerModel(name: 'Beta', url: 'https://betaServer.com/'),
];
ServerModel selectedServer;
#override
initState() {
super.initState();
selectedServer = serverModels[0];
}
#override
Widget build(BuildContext context) {
return DropdownButton<ServerModel>(
value: selectedServer,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: Colors.purple[700]),
underline: Container(
height: 2,
color: Colors.purple[700],
),
onChanged: (ServerModel newServer) {
setState(() {
selectedServer = newServer;
});
},
items: serverModels.map((ServerModel map) {
return new DropdownMenuItem<ServerModel>(
value: map, child: Text(map.name));
}).toList(),
);
}
}
Tested, working interactive answer on dartpad:
https://dartpad.dev/153bad9baac64382e27bc41cdc8131c9
You're facing an equality problem.
In Dart, non-primitive types like SizedBox, List, and in your case, ServerModel are compared to each other using referential equality, meaning that they are equal to each other if they have the same reference. That is, they are the same instance.
So this code will print false:
print(ServerModel(name: 'Default', url: 'https://defaultServer.com/') == ServerModel(name: 'Default', url: 'https://defaultServer.com/'));
// TL;DR
print(ServerModel(xyz) == ServerModel(xyz)); // false
The solution would be to override the equality operator for your class ServerModel.
class ServerModel {
ServerModel({this.name, this.url});
ServerModel.empty() {
this.name = null;
this.url = null;
}
String name;
String url;
#override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ServerModel && other.name == name && other.url == url;
}
#override
int get hashCode => name.hashCode ^ url.hashCode;
}
Now it should work.
PRO TIP: Use equatable to automatically generate equality and hashcode.
Related
I am trying to create a setting page. In it, I would like to have several switches. The user will use them to manage the Permissions, for Camera, Micro and more.
Here is the code I have written, but clearly something is wrong as it is not working properly.
When I tap on the switch, it does not change from On to Off. Also, it does not change the permissions.
I am using:
import 'package:settings_ui/settings_ui.dart';
import 'package:permission_handler/permission_handler.dart';
SizedBox(
height: height,
child: SettingsList(
sections: [
SettingsSection(
title: Text(tPermissions,style: TextStyle(color: Theme.of(context).primaryColor,fontWeight: FontWeight.bold, fontSize: 13.0),
),
tiles: <SettingsTile>[
SettingsTile.switchTile(
leading: const Icon(Icons.camera),
onToggle: (value) {
setState(() {
value = !value;
if (value = false) {
Permission.camera.isDenied;
print (Permission.camera.isDenied);
} else
{
print (Permission.camera.isGranted);
Permission.camera.isGranted;
}
});
},
initialValue: false,
trailing: ChangeCameraWidget(),
title: const Text(tCamera),
),
class ChangeCameraWidget extends StatefulWidget {
const ChangeCameraWidget ({Key? key}) : super(key: key);
#override
State<ChangeCameraWidget> createState() => _ChangeCameraWidgetState();
}
class _ChangeCameraWidgetState extends State<ChangeCameraWidget> {
#override
Widget build(BuildContext context) {
return Switch.adaptive(
value: false,
onChanged: (value) {
if (value = false) {value = true;} else {value = false;}
});
}
}
I am getting this error in the console when I am trying to use flutter DropdownButton Widget.
package:flutter/src/material/dropdown.dart': Failed assertion: line 1252 pos 12: 'widget.items!.where((DropdownMenuItem item) => item.value == widget.value).length == 1': is not true.
There is a long traceback...
Here I am adding small code sample that will reproduce this error... Anyone can simply copy paste in main.dart file
// flutter import
import 'package:flutter/material.dart';
void main() {
runApp(const BugReportApp());
}
class BugReportApp extends StatefulWidget {
const BugReportApp({Key? key}) : super(key: key);
#override
State<BugReportApp> createState() => _BugReportAppState();
}
class _BugReportAppState extends State<BugReportApp> {
final TextEditingController _dropdownController = TextEditingController();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bug Report',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Flex(direction: Axis.vertical, children:[
DropdownButton<String>(
value: _dropdownController.text == ""
? null
: _dropdownController.text,
items: ["hello, world", "how are you", "goodbye"]
.map((_value) => DropdownMenuItem<String>(
child: Text(
_value,
)))
.toList(),
onChanged: (_value) {
setState(() {
_dropdownController.text = _value ?? _dropdownController.text;
});
},
),
],),
);
}
}
I was expecting dropown to work normally but, I don't know why it didn't.
You are missing value on DropdownMenuItem.
.map((_value) => DropdownMenuItem<String>(
value: _value, // this
child: Text(
_value,
)))
Also make sure to use Scaffold on home.
Try this code, also added some explanation in the code:
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _dropdownController = TextEditingController();
String? dropDownValue = 'hello, world'; // add one value as the defaul one which must exists in the dropdown value
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
Flex(direction: Axis.vertical, children:[
DropdownButton<String>(
value: dropDownValue, // this place should not have a controller but a variable
onChanged: (_value) {
setState(() {
dropDownValue = _value;
});
},
items: ["hello, world", "how are you", "goodbye"]
.map<DropdownMenuItem<String>>((String _value) => DropdownMenuItem<String>(
value: _value, // add this property an pass the _value to it
child: Text(_value,)
)).toList(),
),
])
],
),
);
}
}
please add the VALUE field in both DropdownMenuItem and DropdownButton to prevent error
I have the following custom dropdown widget that I have created.
class CustomDropdown extends StatefulWidget {
final Color? textColor;
final Color? backgroundColor;
final Color? iconColor;
final bool? boldText;
final Object? initialValue;
final List<DropdownMenuItem<Object?>> itemList;
final Function(Object?) onItemSelect;
const CustomDropdown({
Key? key,
this.textColor,
this.backgroundColor,
this.boldText,
this.iconColor,
required this.initialValue,
required this.itemList,
required this.onItemSelect,
}) : super(key: key);
#override
_CustomDropdownState createState() => _CustomDropdownState();
}
class _CustomDropdownState extends State<CustomDropdown> {
late Object? _dropdownValue;
late bool _boldText;
#override
void initState() {
super.initState();
_dropdownValue = widget.initialValue;
_boldText = widget.boldText ?? false;
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: 20,
child: DropdownButtonHideUnderline(
child: DropdownButton<Object?>(
value: _dropdownValue,
icon: Icon(
Icons.expand_more_outlined,
color: widget.iconColor ?? (widget.textColor ?? Colors.black),
),
dropdownColor: widget.backgroundColor ?? Colors.white,
style: TextStyle(
color: widget.textColor ?? Colors.black,
fontWeight: _boldText ? FontWeight.bold : FontWeight.normal,
),
items: widget.itemList,
onChanged: (value) {
setState(() => _dropdownValue = value);
widget.onItemSelect(value);
},
),
),
);
}
}
Then I Instantiate the above widget I have created in a separate dart file as shown below,
class CurrencyDropdown extends StatelessWidget {
const CurrencyDropdown({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: CustomDropdown(
initialValue: Currency(id: 1, displayText: 'USD'), <------------ This line throws an error
boldText: true,
iconColor: ColorData.disabledTextColor,
itemList: [
DropdownMenuItem(
child: Text('USD'),
value: Currency(id: 1, displayText: 'USD'),
),
DropdownMenuItem(
child: Text('LKR'),
value: Currency(id: 2, displayText: 'LKR'),
),
],
onItemSelect: (_) {},
),
);
}
}
In the above code, if I replace the value I have pointed with an arrow with null everything works fine. However, if I provide the value shown in the above code snippet, it throws an error.
What the error text says is,
There should be exactly one item with [DropdownButton]'s value: { id: 1, displayText: USD }.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart'
Furthermore it shows the following code snippet,
Failed assertion: line 915 pos 15:
'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'
I also came across the below stack overflow posts but could not find a solution,
Flutter Stateful Widgets and Generics
How to implement a dropdown list in flutter?
Flutter dropdown fails when class is provided instead of a string as
value
Can someone please help me? I would really appreciate it!
Your initialValue didn't match any value in itemList, try code below:
class CurrencyDropdown extends StatelessWidget {
CurrencyDropdown({Key? key}) : super(key: key);
List<Currency> list = [
Currency(id: 1, displayText: 'USD'),
Currency(id: 2, displayText: 'LKR'),
];
#override
Widget build(BuildContext context) {
return Center(
child: CustomDropdown(
initialValue: list[0],
boldText: true,
iconColor: Colors.grey,
itemList: list
.map(
(e) => DropdownMenuItem(
child: Text(e.displayText),
value: e,
),
)
.toList(),
onItemSelect: (_) {},
),
);
}
}
I have a listview.builder in flutter and every item of the list has a dropdown now whenever I select one dropdown value of every dropdown changes. how can I fix this problem in flutter?
Ok, after spending a couple of hours on this and not finding a satisfactory answer (but a lot of hints) I worked it out.
I made a new StatefulWidget class that wraps the DropdownButton. It is instantiated with the List of items for the dropdown.
listview_dropdownbutton.dart
import 'package:flutter/material.dart';
class ListviewDropdownButton extends StatefulWidget {
final List<dynamic> sizes;
const ListviewDropdownButton({
Key? key,
required this.sizes,
}) : super(key: key);
#override
State<ListviewDropdownButton> createState() => _ListviewDropdownButton();
}
class _ListviewDropdownButton extends State<ListviewDropdownButton> {
List<dynamic>? _sizes;
String _currentSize = '';
#override
Widget build(BuildContext context) {
_sizes = _sizes ?? widget.sizes;
_currentSize = _currentSize != '' ? _currentSize : widget.sizes[0];
return DropdownButton<dynamic>(
value: _currentSize,
style: const TextStyle(
color: Colors.green,
),
items: _sizes!.map<DropdownMenuItem<dynamic>>((dynamic size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (dynamic size) {
if (_currentSize != size) {
setState(() {
_currentSize = size!;
});
}
},
);
}
}
In the parent widget, just include the class and use it where you'd put the DropdownButton.
Here's a working example.
main.dart
import 'package:flutter/material.dart';
import 'listview_dropdownbutton.dart';
void main() => runApp(const DropdownButtonApp());
class DropdownButtonApp extends StatelessWidget {
const DropdownButtonApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('DropdownButton In ListView')),
body: Center(
child: DropdownButtonExample(),
),
),
);
}
}
class DropdownButtonExample extends StatelessWidget {
DropdownButtonExample({super.key});
final List<String> _items = <String>['Shirt', 'T-Shirt', 'Pants', 'Blouse', 'Coat'];
final List<String> _sizes = <String>['Small', 'Medium', 'Large', 'X-Large'];
String _currentSize = 'Small';
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _items.length,
itemBuilder: (
BuildContext context,
int index,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_items[index]),
Row(
children: [
ListviewDropdownButton(
sizes: _sizes,
),
DropdownButton<String>(
value: _currentSize,
style: const TextStyle(
color: Colors.red,
),
items: _sizes.map<DropdownMenuItem<String>>((String size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (String? size) {
if (_currentSize != size) {
// setState(() {
_currentSize = size!;
// });
}
},
),
],
),
const Divider(
thickness: 2,
height: 2,
),
],
);
},
);
}
}
To illustrate it works, I put both the ListviewDropdownButton and a regular DropdownButton in the ListView.
I added String _currentSize = 'Small'; and the onChanged method to show the regular DropdownButton does not work. It never changes from "Small", which was my original problem.
for an authentication I would like to recover the base_url of the company chosen from a drop-down list, but I can't do it, being a beginner a little help will be welcome.
here is the code of the dropdownlist:
class DropDown extends StatefulWidget {
DropDown({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<DropDown> {
String _mySelection;
String _myBaseUrl;
List<Map> _myJson = [{"id":2,"society":"test","baseUrl":"url.com"},{"id":1,"society":"planeef","baseUrl":"url.com"}];
#override
Widget build(BuildContext context) {
return Container(
child: new DropdownButton<String>(
isDense: true,
hint: new Text("Select"),
value: _mySelection,
onChanged: (String newValue) {
setState(() {
_mySelection = newValue;
});
},
items: _myJson.map((Map map) {
return new DropdownMenuItem<String>(
value: map["id"].toString(),
child: new Text(
map["society"],
),
);
}).toList(),
),
);
}
}
Check the code below. You can use singleWhere function to retrieve the element from the id value you are getting from the dropdown and then read baseUrl from the element.
The singleWhere function matches and returns a single element from the list based on the condition we provide.
Note -
The singleWhere function throws an error by default if there are duplicates or no element is found. You might need to also pass the orElse parameter to singleWhere or add some error handling in that case.
More about that can be found here.
class _MyHomePageState extends State<MyHomePage> {
String _mySelection;
List<Map> _myJson = [{"id":2,"society":"test","baseUrl":"url.com"},{"id":1,"society":"planeef","baseUrl":"url.com"}];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: new DropdownButton<String>(
isDense: true,
hint: new Text("Select"),
value: _mySelection,
onChanged: (String newValue) {
Map<dynamic,dynamic> _myElement = _myJson.singleWhere((test) => test["id"] == int.parse(newValue));
print(_myElement["baseUrl"]);
//Add the above two lines
setState(() {
_mySelection = newValue;
});
},
items: _myJson.map((Map map) {
return new DropdownMenuItem<String>(
value: map["id"].toString(),
child: new Text(
map["society"],
),
);
}).toList(),
),
)
);
}
}