I have a problem with a Flutter dropdown. When I select one of the items it throws an error:
Another exception was thrown: 'package:flutter/src/material/dropdown.dart': Failed assertion: line 481 pos 15: 'value == null || items.where((DropdownMenuItem item) => item.value == value).length == 1': is not true.
I was searching and people tell that this error is produced because the selected element doesn't belong to the original list but after some debug I can see it does. I can't find the source of this error, so I would appreciate any help.
Here is my code
FeedCategory model
import 'package:meta/meta.dart';
class FeedCategory {
static final dbId = "id";
static final dbName = "name";
int id;
String name;
FeedCategory({this.id, @required this.name});
FeedCategory.fromMap(Map<String, dynamic> map)
: this(
id: map[dbId],
name: map[dbName],
);
Map<String, dynamic> toMap() {
return {
dbId: id,
dbName: name,
};
}
@override
String toString() {
return 'FeedCategory{id: $id, name: $name}';
}
}
Widget
import 'package:app2date/repository/repository.dart';
import 'package:app2date/model/FeedSource.dart';
import 'package:app2date/model/FeedCategory.dart';
import 'package:app2date/util/ui.dart';
import 'package:flutter/material.dart';
class ManageFeedSource extends StatefulWidget {
ManageFeedSource({Key key, this.feedSource}) : super(key: key);
final FeedSource feedSource;
@override
_ManageFeedSource createState() => new _ManageFeedSource();
}
class _ManageFeedSource extends State<ManageFeedSource> {
FeedCategory _feedCategory;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('New Feed'),
),
body: new FutureBuilder(
future: Repository.get().getFeedCategories(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<FeedCategory> categoriesList = snapshot.data;
if (categoriesList != null) {
return new DropdownButton<FeedCategory>(
hint: Text('Choose category...'),
value: _feedCategory,
items: categoriesList.map((FeedCategory category) {
return DropdownMenuItem<FeedCategory>(
value: category,
child: Text(category.name),
);
}).toList(),
onChanged: (FeedCategory category) {
print('Selected: $category');
setState(() {
_feedCategory = category;
});
},
);
} else {
return Container(
decoration: new BoxDecoration(color: Colors.white),
);
}
},
),
);
}
@override
void initState() {
super.initState();
}
}
Repository getFeedCategories method
Future<List<FeedCategory>> getFeedCategories() async {
return await database.getFeedCategories();
}
Database getFeedCategories method
Future<List<FeedCategory>> getFeedCategories() async {
var dbClient = await db;
var query = "SELECT * FROM $feedCategoryTableName;";
var result = await dbClient.rawQuery(query);
List<FeedCategory> feedCategories = [];
for (Map<String, dynamic> item in result) {
feedCategories.add(new FeedCategory.fromMap(item));
}
return feedCategories;
}
The categoriesList content and the selected category (debugger)
I think I've figured out your issue. It stems from how you're using FutureBuilder.
This is what I think is going on:
The future completes, and the list is built, etc with the following items:
obj 123 ==> FeedCategory(Prueba)
, obj 345 ==> FeedCategory(Categories 2)
You select the item from the dropdown, setState() is called
Your _feedCategory is now equal to obj 123 ==> FeedCategory(Prueba)
Widget is rebuilt. It does another call to getFeedCategories()
Future completes, list is built etc with the following items
obj 567 ==> FeedCategory(Prueba)
, obj 789 ==> FeedCategory(Categories 2)
items.where((DropdownMenuItem item) => item.value == value).length == 1
, but length == 0 because obj 123 ==> FeedCategory(Prueba)
is not found.There are a couple of solutions to your problem. One would be to add an equals operator to your FeedCategory
class that compares the category and/or id.
Another would be to separate the Future used in the futurebuilder from the part that changes - you could do this either by keeping the future as a member variable (maybe instantiate it in initState?), or by making the inner part of the builder into it's own Stateful widget (in which case you could probably make ManageFeedSource a StatelessWidget). I'd recommend the last option.
Let me know if that doesn't solve your issue, but I'm pretty sure that's the reason it's doing what it's doing.
To complete the rmtmckenzie answer, here is the code you should put in your FeedCategory object:
bool operator ==(o) => o is FeedCategory && o.name == name;
int get hashCode => name.hashCode;
Edit: A link that explains what hashCode is made for in Flutter
i got this error when i initialized the dropdown with a value that was not actually in the dropdown list which was unclear from the error message.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With