Flutter populate dropdown button from firestore array - flutter

How do I populate this array list into a dropdown button?
I want to populate this list of the location to the dropdown button.
[here is how my database looks like]
https://i.stack.imgur.com/S5yDh.png

I know, it's too late to answer your question. But maybe, mine is for anybody who seeks an answer.
Just use streambuilder to stream firestore data in your dropdown.
Here is my example:
StreamBuilder(
stream: Firestore.instance.collection('your collectionName').document('yourDocument').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return textCustom('Loading', Colors.black87, 16, 'Montserrat');
} else {
final data = snapshot.data['data'];
return Theme(
data: Theme.of(context).copyWith(
canvasColor: Color(0xFF221F1F),
),
child: DropdownButtonFormField(
style: TextStyle(color: Colors.white70),
value: _value,
items: data
.map<DropdownMenuItem<String>>(
(x) => DropdownMenuItem(
child: textCustom(
x, Colors.white, 16, 'Montserrat'),
value: '${x}',
),
)
.toList(),
onChanged: (val) => setState(() {
_value = val;
}),
),
);
}
},
)

I recommend the flutter_form_bloc package. It's very helpful and you can asynchronously add items to a dropdown array so you can fetch them from an API or for your needs, Firebase.
If you do not want to use a package however, you could create an empty list and assign it to the items parameter initially. In your initState method, you could then populate that list with items and then call setState.

This is not the best way, but gets the work done.
Create a temporary list, then use that list to do a for each.
tempList = returnedDoc['location'];
tempList.forEach((element) {
Locations.add(element);
}),

You can create a dropdown button in following way.
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String dropdownValue;
List<String> listOfStrings = ["apple", "banana", "strawberry", "cherry"];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButton<String>(
value: dropdownValue,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: listOfStrings.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
);
}
}

Related

How to call a form from an 'Add New' DropdownButtonFormField

I'm a degreed 25 yr computer scientist but new to Flutter and mobile apps in general.
I am building forms now and want to append an 'Add New' item at the bottom of a list of existing members in a DropDownButtonFormField. If a user selects the 'Add New' item, I'd like to call a 'Register New Member' form for them to fill out. When submitted, I'd like to return the user to the original form, populate the new member info into the field they were working on, and continue with the rest of the form.
Sounds reasonable, right?
The only thing I've tried thus far is using the onChanged: event using a Hero as shown below.
This doesn't call the MemberForm() at all, but does populate 'Add New Contact' in the field.
I use this same Hero approach when adding new members from a Button and it works fine.
Any help would be appreciated!
DropdownButtonFormField(
decoration: InputDecoration(...),
value: mainContact,
style: const TextStyle(fontSize: 20, color: Colors.black54),
items: members
.map((member) => DropdownMenuItem<String>(
value: member,
child: Text(member),
))
.toList(),
onChanged: (member) =>
member == 'Add New Contact' ?
const Hero(tag: 'Hero-Member', child: MemberForm()) :
setState(() => mainContact = member),
validator: (value) {...},
),
It depends on UX how you like to add new item. but make sure to await and then setState to update main ui. This demo will show the flow with modalBottomSheet
class TestWidget extends StatefulWidget {
const TestWidget({super.key});
#override
State<TestWidget> createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
List<String> items = ['Add New Contact', "A", "B"];
String? mainContact;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
DropdownButtonFormField(
items: items
.map(
(e) => DropdownMenuItem(
value: e,
child: Text(e),
),
)
.toList(),
onChanged: (member) async {
if (member == 'Add New Contact') {
const Hero(tag: 'Hero-Member', child: Text("MemberForm"));
final TextEditingController controller =
TextEditingController();
final String newData = await showModalBottomSheet(
context: context,
builder: (context) => Column(
children: [
TextField(
controller: controller,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(controller.text);
},
child: Text("Save"),
),
],
));
//you can also get text directly from `controller`
if (newData.isNotEmpty) {
items.add(newData);
}
}
mainContact = member;
setState(() {});
},
)
],
),
);
}
}

Dropdownbutton Statemanagement - Flutter

I want to create an evaluation app in flutter with multiple dropdownbuttons. The dropdownbuttons should contain a text with a value, for example: dropdownbutton1: "Text"; Value(2), dropdownbutton2: "Text"; Value2(4) and there is also an another button "evaluate", if i click on the "evaluate" button it should go to the next screen and the next screen displays the total of the value (it should be 6= value(2) + value2(4).
My next thought would be statemanagement, but i dont know how to do it right now.
I searched everywhere in the internet but couldnt find anything.
I am new to flutter. Is there a way to do it with statemanagement and how it could be look like?
Definitely you should implement a flavor of state management, both for the communication between the pages as well as to feed data to the dropdown menus, and holding on to the selected values from the dropdowns.
My suggestion would be to go with something as simple as the Provider state management strategy, and leverage the widgets that facilitate listening to changes occurring on the application. This strategy makes your app more decouples, maintains a clean separation of concerns and allows for good maintenance.
You can create a Provided service (i.e. DropdownService) that holds to the values of the dropdown menus as a list of dropdown options, as well as two properties that hold on to the selected values from the dropdowns (i.e. selectedFirstOption, selectedSecondOption). Upon triggering changes on both DropDownButton widgets, you can trigger a notification for the widget to rebuild itself, updating its selections, and holding on to the selected value.
A button will trigger the evaluation and navigation to the next page, only if both selectedFirstOption and selectedSecondOption are not null, then the next page also consumes the provided DropdownService, pulls the data persisted for both selections, add the values and displays it to the user.
I threw something together as an example to illustrate my point below:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => DropdownService()
)
],
child: MyApp()
)
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<DropdownService>(
builder: (context, dropdownService, child) {
return Container(
padding: const EdgeInsets.all(30),
child: Column(
children: [
const Text('First dropdown:'),
DropdownButton<DropdownOption>(
value: dropdownService.selectedFirstOption,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
onChanged: (DropdownOption? newValue) {
dropdownService.selectedFirstOption = newValue!;
},
items: dropdownService.firstDropdown.map((DropdownOption option) {
return DropdownMenuItem<DropdownOption>(
value: option,
child: Text(option.text!),
);
}).toList(),
),
const SizedBox(height: 20),
const Text('Second dropdown:'),
DropdownButton<DropdownOption>(
value: dropdownService.selectedSecondOption,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
onChanged: (DropdownOption? newValue) {
dropdownService.selectedSecondOption = newValue!;
},
items: dropdownService.secondDropdown.map((DropdownOption option) {
return DropdownMenuItem<DropdownOption>(
value: option,
child: Text(option.text!),
);
}).toList(),
),
const SizedBox(height: 20),
Material(
color: Colors.amber,
child: TextButton(
onPressed: dropdownService.selectedFirstOption != null
&& dropdownService.selectedSecondOption != null ? () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => NextPage())
);
} : null,
child: const Text('Evaluate', style: TextStyle(color: Colors.black)
)
)
)
]
)
);
}
);
}
}
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
DropdownService dropdownService = Provider.of<DropdownService>(context, listen: false);
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('The result of ${dropdownService.selectedFirstOption!.text!} and ${dropdownService.selectedSecondOption!.text!}:'),
const SizedBox(height: 20),
Text('${dropdownService.selectedFirstOption!.value! + dropdownService.selectedSecondOption!.value!}',
style: const TextStyle(fontSize: 50)
),
],
),
),
);
}
}
class DropdownService extends ChangeNotifier {
List<DropdownOption> firstDropdown = [
DropdownOption(text: 'Value (5)', value: 5),
DropdownOption(text: 'Value (6)', value: 6),
DropdownOption(text: 'Value (7)', value: 7),
];
DropdownOption? _selectedFirstOption; // = DropdownOption(text: 'Select Value', value: -1);
DropdownOption? _selectedSecondOption; // = DropdownOption(text: 'Select Value', value: -1);
DropdownOption? get selectedFirstOption => _selectedFirstOption;
DropdownOption? get selectedSecondOption => _selectedSecondOption;
set selectedFirstOption(DropdownOption? value) {
_selectedFirstOption = value;
notifyListeners();
}
set selectedSecondOption(DropdownOption? value) {
_selectedSecondOption = value;
notifyListeners();
}
List<DropdownOption> secondDropdown = [
DropdownOption(text: 'Value (1)', value: 1),
DropdownOption(text: 'Value (2)', value: 2),
DropdownOption(text: 'Value (3)', value: 3),
];
}
class DropdownOption {
String? text;
double? value;
DropdownOption({ this.text, this.value });
}
If you run this code (also provided as a Gist) through DartPad.dev, you should see the following output:

Custom Widget not Rendering

I have made a custom DropDown Picker the problem is when I switch it, the widget does not get rendered
There are 2 Dropdowns on the UI. In different cases, the child dropDown may or may not be visible.
The problem only occurs if I have both parent and child dropdowns and in the next case, the two dropdowns are both visible.
These are the below cases of how my Dynamic UI is render
case 1 ) DropDown1 and Drop DropDown2 on the UI (Drop Down 2 is parent widget)
when the user clicks on dropDown 2 items the Main UI gets rendered.
(Drop Down 2 items Minutes, Hours, Day, Week)
DropDown 1 item changes as per drop down 2 )
class CustomDropDown extends StatefulWidget {
final List<String> dropDownList;
final defaultVal;
final Function selectedItem;
final customDropDownId;
final isExpanded;
final dropDownType;
const CustomDropDown(
{Key? key,
required this.dropDownList,
required this.defaultVal,
required this.selectedItem,
this.customDropDownId,
this.isExpanded = false,
required this.dropDownType})
: super(key: key);
#override
_CustomDropDownState createState() => _CustomDropDownState();
}
class _CustomDropDownState extends State<CustomDropDown> {
var chosenValue;
#override
void initState() {
super.initState();
print("initState");
chosenValue = widget.defaultVal;
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
#override
void dispose() {
super.dispose();
print("dispose");
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
print("dropDownList ${widget.dropDownList} defaultVal ${widget.defaultVal} chosenValue ${chosenValue} ");
if (widget.dropDownType == DropDownType.DROPDOWN_WITH_ARROW) {
return Material(
elevation: 10,
color: foreground_color,
borderRadius: BorderRadius.circular(10.r),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w),
child: DropdownButton<String>(
value: chosenValue,
isExpanded: widget.isExpanded,
dropdownColor: foreground_color,
icon: const Icon(Icons.keyboard_arrow_down_rounded),
borderRadius: BorderRadius.circular(10.r),
underline: const SizedBox(),
style: const TextStyle(color: Colors.white),
iconEnabledColor: Colors.white,
items: widget.dropDownList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(color: Colors.white),
),
);
}).toList(),
onChanged: (String? value) {
if (value != null) {
setState(() {
chosenValue = value;
widget.selectedItem(chosenValue, widget.customDropDownId);
});
}
},
),
),
);
}
Parent Widget
Widget repeatEveryWidget(chosenValue) {
if (chosenValue == dropDownJobList[0] ||
chosenValue == dropDownJobList[1]) {
bool isMinutesWidget = chosenValue == dropDownJobList[0];
List<String> dropDownList = isMinutesWidget ? minutesList : hourList;
return CustomDropDown(
isExpanded: false,
dropDownList: dropDownList,
defaultVal:
isMinutesWidget ? defaultMinuteSelected : defaulHourSelected,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem: (String selectedVal, DropDownsType dropDownId) {
if (isMinutesWidget) {
defaultMinuteSelected = selectedVal;
} else {
defaulHourSelected = selectedVal;
}
},
customDropDownId: DropDownsType.CustomId,
);
} else {
return const SizedBox();
}
}
Parent Calling
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(REPEAT_EVERY),
SizedBox(
width: 10.w,
),
repeatEveryWidget(chosenValue),
SizedBox(
width: 10.w,
),
CustomDropDown(
dropDownList: dropDownCustomList,
defaultVal: chosenValue,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem:
(String selectedVal, DropDownsType dropDownId) {
setState(() {
chosenValue = selectedVal;
});
},
customDropDownId:
DropDownsTypeRepeatPicker,
),
],
),
)
Output
If the user selects item 1 Minute and then selects any item other than hours the child drop down gets removed from UI. But when the user selects hours after a minute the Items in Child widget renders but the defaultValue of this does not pick a new value it retains the old data that was picked in minutes as the UI has not been destroyed.
The answer to the above question lies in statement management in a flutter.
As there are two lists which has string object and some data are identical like "5","10"
const List<String> minutesListConfig = ['5', '10', '15', '30', '45', '60'];
const List<String> hourListConfig = ['1', '2', '3','4', '5', '6', '7','8', '9', '10', '11','12'];
As said by the flutter team every widget has an element that keeps track of its state whenever you try to render identical object type in dropdown list it will not render the new list.
If you want the widget tree to make sure to render the dropdown widget in 2 different ways then you will have to use KEY
In this case, the Object key can be used to make sure in background flutter makes 2 different dropdown lists for both the cases and does not render the dropdown as 1 widget.
return CustomDropDown(
key: ObjectKey(chosenValue),
isExpanded: false,
dropDownList: dropDownList,
defaultVal:
isMinutesWidget ? defaultMinuteSelected : defaulHourSelected,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem: (String selectedVal, DropDownsType dropDownId) {
if (isMinutesWidget) {
defaultMinuteSelected = selectedVal;
} else {
defaulHourSelected = selectedVal;
}
},
customDropDownId: DropDownsType.CustomId,
);
if (widget.dropDownType == DropDownType.DROPDOWN_WITH_ARROW) {
return Material(
elevation: 10,
color: foreground_color,
borderRadius: BorderRadius.circular(10.r),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w),
child: DropdownButton<String>(
key:widget.key
value: chosenValue,
isExpanded: widget.isExpanded,
dropdownColor: foreground_color,
icon: const Icon(Icons.keyboard_arrow_down_rounded),
borderRadius: BorderRadius.circular(10.r),
underline: const SizedBox(),
style: const TextStyle(color: Colors.white),
iconEnabledColor: Colors.white,
items: widget.dropDownList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(color: Colors.white),
),
);
}).toList(),
onChanged: (String? value) {
if (value != null) {
setState(() {
chosenValue = value;
widget.selectedItem(chosenValue, widget.customDropDownId);
});
}
},
),
),
);
Flutter team video on keys
https://www.youtube.com/watch?v=kn0EOS-ZiIc

Flutter : how to pass value to Stateful widget?

I am new to flutter, I can easily create a stateless widget and require value to be set when creating an object (#required this.value)
However, I am a bit lost on how to do it with a stateful widget.
When looking at the code below, I want to be able to get "hint" value from the object created, into the stateful widget constructor, and down to the drop-down menu. I hope this makes sense.
class MyDropdownButton extends StatefulWidget { MyDropdownButton({
this.hint, }); String hint; #override _MyDropdownButtonState createState() => _MyDropdownButtonState(); }
class _MyDropdownButtonState extends State<MyDropdownButton> { String dropdownValue; String hint;
#override Widget build(BuildContext context) {
return Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'TO',
style: TextStyle(color: kColourGreyText),
),
DropdownButton<String>(
value: dropdownValue != null ? dropdownValue : null,
hint: Text(hint),
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: kColourGreyText),
underline: Container(
height: 2,
color: kColorPrimary,
),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items:
accountNameList.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
); } }
You can access widget properties from a state directly. Try widget.hint
Found multiple problems with your code,
class MyDropdownButton extends StatefulWidget {
MyDropdownButton({#required this.hint}); // use #required for required parameters, like this one here
final String hint; // use final
#override
_MyDropdownButtonState createState() => _MyDropdownButtonState();
}
// below line you missed `<MyDropdownButton>` after `State`,
// I'd suggest using a recommended IDE with a recommended Flutter
// Extention to generate code samples
class _MyDropdownButtonState extends State<MyDropdownButton> {
String dropdownValue;
// I added below 3 lines to avoid the errors in this sample code
List<String> accountNameList = [];
Color kColorPrimary = Colors.blue;
Color kColourGreyText = Colors.grey;
#override
Widget build(BuildContext context) {
// You don't need `Expanded` here, just wrap it with `Row` only
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('TO', style: TextStyle(color: kColourGreyText)),
DropdownButton(
value: dropdownValue != null ? dropdownValue : null,
// when accessing parsed values in Statful,
// use widget.<variable name>
hint: Text(widget.hint),
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: kColourGreyText),
underline: Container(height: 2, color: kColorPrimary),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: accountNameList.map((String value) {
return DropdownMenuItem(value: value, child: Text(value));
}).toList(),
),
],
);
}
}
and again, I'd suggest using a recommended IDE with a recommended Flutter Extention to generate code samples

clear DropDownButton flutter

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.