I'm having an issue with duplicate DropdownMenuItem while getting the menu item's value from Firestore.
I want to get all the 'from' attributes values from 'routes' collection excluding the duplicates. And i want to show them inside my DropDownButton.
My Sample Code:
late final Stream<QuerySnapshot> routes = Database().getRoutes();
StreamBuilder<QuerySnapshot>(
stream: routes,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
final data = snapshot.requireData;
fromValue.value = data.docs[0]['from'];
destinationValue.value = data.docs[0]['to'];
return Column(
children: [
Obx(() {
return DropdownButton(
// Initial Value
value: fromValue.value,
// Down Arrow Icon
icon: const Icon(Icons.keyboard_arrow_down),
alignment: AlignmentDirectional.center,
underline: Container(
height: 1,
color: Colors.deepPurple.shade100,
),
// list of items
items: data.docs.map(
(DocumentSnapshot doc) {
return DropdownMenuItem<
String>(
value: doc['from']
.toString(),
child:
Text(doc['from']));
}).toList(),
// After selecting the desired option,it will
// change button value to selected value
onChanged: (newValue) {
fromValue.value = newValue.toString();
},
);
}),
],
);
}),
Method Used To Fetch Firestore Data:
getRoutes() {
final Stream<QuerySnapshot> routes = db
.collectionGroup("routes")
.snapshots();
return routes;
}
Related
i want to build Dropdown list from Future,here is my function for simple list view which is working,,but how to populate drop down list from it, i am really confuse about this list map etc in flutter coming from php background,
child: FutureBuilder(
future:userApi.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.data == null){
return Container(
child: Center(
child: Text("Loading...")
)
);
} else {
return Container(
child: DropdownButton(
items: snapshot.data.map((item) {
return DropdownMenuItem(child: Text(item.title));
}).toList(),
onChanged: (value){},
)
);
}
},
),
class UserApi{
Future<List<User>>getUsers() async {
var data = await http.get("https://jsonplaceholder.typicode.com/albums/");
var jsonData = json.decode(data.body);
List<User> users = [];
for(var u in jsonData){
User user = User(u["id"], u["title"]);
users.add(user);
}
return users;
}
class User {
final int id;
final String title;
User(this.id,this.title);
}
Ok I just read the comment left above if your problem is getting your data then this might not help you but if snapshot has data this will work
//initialize this outside your build method
String dropDownValue;
FutureBuilder(
future:userApi.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? Container(
child: DropdownButton<String>(
hint: Text(dropDownValue ?? 'Make a selection'),
items: snapshot.data.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>(
value: item.title,
child: Text(item.title),
);
}).toList(),
onChanged: (value) {
setState(() {
dropDownValue = value;
print(value);
});
},
),
)
: Container(
child: Center(
child: Text('Loading...'),
),
);
},
),
you can call the map function on any list converting the elements of the list.
return DropdownButton(
items: snapshot.data.map((item) {
return DropdownMenuItem(child: Text(item.title));
}).toList(),
onChanged: (value){},
);
if you can see here we're converting the list snapshot.data to another list of DropdownMenuItem type by calling map on snapshot.data the map function takes another function that's being called on every element of snapshot.data , map returns an Iterable of type DropdownMenuItem (the returned type of the function being called on every element) and we convert the Iterable to a list by calling toList() on it
I wish that is explanatory enough, the map function is very useful.
i have a DropDownButton which add category for my product. after added product DropDownButton display category which i choosed. I want to refresh or clear DropDownButton after adding product. Better to display hint text.
code:
child: StreamBuilder<List<Categories>>(
stream: categories,
builder: ((context, snapshot) {
if (!snapshot.hasData)
return CircularProgressIndicator();
return DropdownButton(
hint: Text('choose category'),
value: _currentCategory,
items: snapshot.data
.map((doc) => DropdownMenuItem(
child: Text(doc.categoryname),
value: doc,
))
.toList(),
onChanged: (selectedCategory) =>
setState(() {
_currentCategory = selectedCategory;
}),
);
})),
),
SizedBox(height: 15),
RaisedButton(
onPressed: () {
if (_formKeyProduct.currentState.validate()) {
ProductService().addProduct(
_nameproductController.text,
_priceproductController.text,
_currentCategory.categoryname.toString());
_formKeyProduct.currentState.reset();
_nameproductController.clear();
_priceproductController.clear();
}
},
child: Text('add product'),
Since the value you chose is _currentCategory using
setState(() {
_currentCategory = null;
}
)
should do the trick.
EDIT:
I see some downvotes on this. This answer was based on Flutter 1.22.6. If it doesn't work on Flutter 2 that is why.
This is a complete widget including a button to reset the dropdown value. It is based on provider package. The widget include a button to clear selection.
class MyDataDropDown extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return Consumer<HomeModel>(
builder: (context, model, child) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(10)),
child: Row(
children: [
Expanded(
child: DropdownButton<SearchMyData>(
isExpanded: true,
value: model.currentMyData,
onChanged: (final SearchMyData? newValue) {
model.setCurrentMyData(newValue);
},
items: model.data.map<DropdownMenuItem<SearchMyData>>((SearchMyData value) {
return DropdownMenuItem<SearchMyData>(
value: value,
child: Text(MyDataDataUtils.getMyDataLabelDropDown(value), overflow: TextOverflow.ellipsis),
);
}).toList(),
// add extra sugar..
hint: const Text("Pick a data"),
icon: const Icon(Icons.arrow_drop_down),
underline: const SizedBox(),
),
),
IconButton( // clear dropdown button
onPressed: () => model.setCurrentMyData(null),
icon: Icon(Icons.clear))
],
),
);
},
);
}
}
Provider class
class HomeModel extends ChangeNotifier{
....
UnmodifiableListView<SearchTheme> get data => UnmodifiableListView([your data here]);
SearchMyData? _selectedMyData;
SearchMyData? get currentMyData => _selectedMyData;
setCurrentMyData(final SearchMyData? item) {
_selectedMyData = item;
notifyListeners();
}
}
Now you have a complete dropdown including clear selection button.
In my code, I am parsing Json and creating a List of Maps, And for each Map, I want to display them in a dropdown.
here is my code
import 'package:flutter/material.dart';
class ReportFilter extends StatefulWidget {
final Map jsonString;
var globalData;
ReportFilter({#required this.jsonString, this.globalData});
#override
_ReportFilterState createState() => _ReportFilterState();
}
class _ReportFilterState extends State<ReportFilter> {
List<TextEditingController> controllers = [];
TextEditingController controller;
Map<String, Object> globalValues = Map<String, Object>();
String type;
int optionId;
#override
void initState() {
globalValues = widget.globalData;
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Align(
alignment: Alignment.topRight,
child: FlatButton.icon(
label: Text('Filters'),
icon: Icon(Icons.filter_list),
onPressed: () => showModalSheet(),
)),
],
)),
);
}
showModalSheet() {
List<Map<String, Object>> dropdownList = List<Map<String, Object>>();
// here I am fetching data from json and doing some operations and storing the result into the List of maps, i.e dropdownList
// below I showed the contents of the dropdownList, and I am passing this list to the Dropdown menu. The contents of the list is dynamic and it keeps changing, It may contains any no of type value as String in map, or List in map
showModalBottomSheet<void>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter state) {
return createBox(context, dropdownList, state);
});
});
}
createBox(BuildContext context, List<Map<String, Object>> val,
StateSetter setState) {
Widget supporting = buildSupportingWidget(val, setState);
return SingleChildScrollView(
child: LimitedBox(
maxHeight: 300,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildMainDropdown(val, setState),
if (supporting != null) supporting
])));
}
Expanded buildMainDropdown(
List<Map<String, Object>> items, StateSetter setState) {
return Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: type,
hint: Text("Select a type"),
items: items
.map((json) => DropdownMenuItem(
child: Text(json["displayName"]), value: json["type"]))
.toList(),
onChanged: (newType) {
setState(() {
type = newType;
});
},
),
),
);
}
Widget buildSupportingWidget(
List<Map<String, Object>> items, StateSetter setState) {
if (type == "list") {
List<Map<String, Object>> options = items[1]["data"];
return Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: optionId,
hint: Text("Select an entry"),
items: options
.map((option) => DropdownMenuItem(
child: Text(option["displayId"]), value: option["id"]))
.toList(),
onChanged: (newId) => setState(() {
this.optionId = newId;
}),
),
),
);
} else if (type == "string") {
return Expanded(child: TextFormField());
}
return null;
}
}
In show model sheet I am doing some operations to get the List of Maps,
So after executing those, my dropdownList List have data like this,
[
{
"displayName": "Enter value",
"type": "string",
},
{
"displayName": "Enter second value",
"type": "string",
},
{
"id": "si",
"displayName": "Source",
"type": "list",
"value": ["MO"], "searchField": "si",
"data": [
{"id": 1, "displayId": "MO"},
{"id": 2, "displayId": "AO"},
{"id": 3, "displayId": "OffNet"}
]
}
];
It can be multiple strings, or lists depend on the Json I am processing,
Then I am passing the list to create box there I am calling the Dropdown functions, but I am not getting how to create those dropdowns based on the List, and also managing the final data once the user selects the particular item,
like if the user in the first dropdown selects an item that needs a text box, I need to store that entered value somewhere, and also if he selects the list from the first dropdown which results in displaying the other dropdown, I need to Store both the data in somewhere.
if someone has done something like this before, or someone knows this please let me know, Thank you
Issue
I've created a dropdown menu and when users click on certain items, it gives them the option to delete it. However, when I try to get the document ID of the selected item it instead returns the value of what I selected.
Example
When I click on Email in my dropdown menu it prints "Email" which is a field value 'Name' : 'Email' from document DjgTDgbW4CkHMURnz32T. However, when I try and print the document ID of "Email", the field value just gets printed instead.
Tested so far
So far I know that userDocSTRING & userCatSTRING are correct and when I print _selectedCATEGORY, the correct value is also printed. But for some reason when I put .documentID at the end, "Email" gets printed instead. Flutter doctor returns no errors and not error logs get printed either.
Code
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firepass/Assets/Colors/colors.dart';
import 'package:flutter/material.dart';
import 'package:firepass/Firebase/login_auth.dart';
// Instantiate stream for listening to category changes
// Used in all dropdown menus
class CategorySettings extends StatefulWidget {
CategorySettingsSTATE createState() => CategorySettingsSTATE();
}
class CategorySettingsSTATE extends State<CategorySettings> {
var selectedCATEGORY;
bool _deleteABLE = false;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('users')
.document(userDocSTRING.toString())
.collection(userCatSTRING.toString())
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
List<DropdownMenuItem> catItems = [];
for (int i = 0; i < snapshot.data.documents.length; i++) {
DocumentSnapshot snap = snapshot.data.documents[i];
catItems.add(DropdownMenuItem(
child: Text(snap.data['Name']),
value: "${snap.data['Name']}",
));
}
return Column(children: <Widget>[
DropdownButton(
items: catItems.toList(),
onChanged: (categoryVALUE) {
setState(() {
selectedCATEGORY = categoryVALUE;
DeleteButtonSTATE().delete = categoryVALUE;
if (categoryVALUE == 'All') {
_deleteABLE = false;
print(
'Category can be deleted ==== ${_deleteABLE.toString()}');
} else
_deleteABLE = true;
print(categoryVALUE.toString());
});
},
value: selectedCATEGORY,
isExpanded: false),
_deleteABLE == true
? DeleteBUTTON()
: Container(height: 0.0, width: 0.0)
]);
}
});
}
}
class DeleteBUTTON extends StatefulWidget {
DeleteButtonSTATE createState() => DeleteButtonSTATE();
}
class DeleteButtonSTATE extends State<DeleteBUTTON> {
var delete;
Widget build(BuildContext context) {
return Column(children: <Widget>[
Padding(padding: EdgeInsets.all(12)),
IconButton(
icon: Icon(Icons.lightbulb_outline),
onPressed: () {
print(delete.toString());
}),
GestureDetector(
onTap: () {
print('Category ${CategorySettingsSTATE().toString()} Deleted');
Firestore.instance
.collection('users')
.document(userDocSTRING.toString())
.collection(userCatSTRING.toString())
.document(CategorySettingsSTATE().selectedCATEGORY.toString())
.delete();
},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
margin: EdgeInsets.symmetric(horizontal: 80),
elevation: 0,
color: accentRedColor,
child: Padding(
padding: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.delete_forever,
color: whiteCOLOR,
),
Padding(padding: EdgeInsets.symmetric(horizontal: 10)),
Text('Delete',
style:
TextStyle(color: whiteTextCOLOR, fontSize: 12))
]))))
]);
}
}
Based on your comments, try this:
var query = Firestore.instance
.collection('users')
.document(userDocSTRING.toString())
.collection(userCatSTRING.toString())
.where('Name', isEqualTo: _selectedCATEGORY);
var category = (await query.getDocuments()).documents[0].documentID;
But because of how complicated this is, I would consider using the categories' names as their IDs. So instead of a random ID, you'll have "Email" as the ID of the category "Email".
my working with DropdownButton and i facing a problem my value is not taken
is show me null
my code is below
SizedBox(
height: 60.0,
child: new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("Category").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text("Please wait");
var length = snapshot.data.documents.length;
DocumentSnapshot ds = snapshot.data.documents[length - 1];
return new DropdownButton(
items: snapshot.data.documents.map((
DocumentSnapshot document) {
return DropdownMenuItem(
child: new Text(document.data["name"]));
}).toList(),
value: category,
onChanged: (value) {
print(value);
},
hint: new Text("Category"),
style: TextStyle(color: Colors.black),
);
}
),
),
You should read more about StatefulWidget, here you have documentation: https://flutter.io/tutorials/interactive/
To fix your issue just update your category variable and refresh the state.
UPDATE
looks like you forget the value for the item also.
SizedBox(
height: 60.0,
child: new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("Category").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text("Please wait");
var length = snapshot.data.documents.length;
DocumentSnapshot ds = snapshot.data.documents[length - 1];
return new DropdownButton(
items: snapshot.data.documents.map((
DocumentSnapshot document) {
return DropdownMenuItem(
value: document.data["name"],
child: new Text(document.data["name"]));
}).toList(),
value: category,
onChanged: (value) {
print(value);
setState(() {
category = value;
});
},
hint: new Text("Category"),
style: TextStyle(color: Colors.black),
);
}
),
),