How to differentiate checkbox lists in flutter - flutter

I am trying to generate a list of cards in flutter with a checkboxlisttile in it so each item can be checked off individually but it's generated so I cant create a set var for each.
bool _isChecked = false;
CheckboxListTile(
title: Text(itemNameList[index]),
secondary: Icon(itemIconList[index]),
subtitle: Row(
children: [
Text('R ${itemPriceList[index]}'),
Padding(
padding: const EdgeInsets.only(left: 25.0),
child: Text("x2"),
),
],
),
value: _isChecked,
onChanged: (bool? value) {
setState(() {
if(_isChecked == true){
_isChecked = false;
}else{
_isChecked = true;
}
});
},
),
Any tips to make it generate but stay it's own because when clicked it checks all the checkboxlisttiles that is generated at the same time?

Because you are using one variable to handle the checkability of all items, and when you check and item; it update the variable value, and setState update the all items accordingly.
Solution: Your approach is much complex. You are creating list of every attribute of an item. One solution is to create an other list of Booleans which handle the checkability. Second solution is create a list of custom model having all the attribute. For example
class YourModelName{
//if your date type is different use that
String name = '';
String icon = '';
String price = '';
bool isChecked = false;
YourModelName({
//user #required if you are not using null safety
required this.name,
required this.icon,
required this.price,
required this.isChecked
});
}
And usage of model list will be as
CheckboxListTile(
title: Text(modelsList[index].name),
secondary: Icon(modelsList[index].icon),
subtitle: Row(
children: [
Text('R ${modelsList[index].price}'),
Padding(
padding: const EdgeInsets.only(left: 25.0),
child: Text("x2"),
),
],
),
value: model.isChecked,
onChanged: (bool? value) {
setState(() {
modelsList[index].isChecked = !modelsList[index].isChecked;
});
},
)

Related

Flutter conditional list

I want to show list of data when i choose from dropdown, but still error LateInitializationError: Field 'listTanaman' has not been initialized. i just initialize the variable and don't assign value to the variable.
i'm initialize the varibale like this
List<Komoditas>? listTanaman;
and assign in this dropdown
DropdownButton(
value: dropdownValue,
items: snapshot.data!.docs
.map((DocumentSnapshot doc) {
return DropdownMenuItem(
value: doc.id,
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(5)),
height: 100,
padding: const EdgeInsets.all(10),
child: Text(doc.get('nama')),
),
);
}).toList(),
onChanged: (newValue) {
setState(() {
dropdownValue = newValue.toString();
kategori = snapshot.data!.docs
.where((newValue) => true)
.first
.get('nama');
});
db
.collection('kategori')
.doc(dropdownValue)
.collection(kategori!)
.snapshots()
.map((event) => listTanaman);
}),
anyone know where is my fault ?
If you need to check if something is initialized, you should be using a nullable variable instead of Late
change this:
Late String Listkategori;//or
Late List Listkategori;
To
String? Listkategori;
List<urtype> Listkategori = [];

flutter search in multiple strings

I have search field that filter my list and it works fine I just want to make small changes to it:
Logic
Back to full list when user clear search field
search also be included of ListTile->subtitle currently only search in title
Code search function is commented for better understanding
List<World> locations = [...]
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
buildSearch(),
Expanded(
child: ListView.builder(
itemCount: locations.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {},
title: Text(locations[index].location),
subtitle: Text(locations[index].country),
),
),
},
),
),
],
),
),
}
Widget buildSearch() => SearchWidget(
text: query,
hintText: 'Search for location',
onChanged: searchLocation,
);
void searchLocation(String query) async {
// currently only search in titles (need to add subtitle as well)
final newLocations = locations.where((location) {
final nameLower = location.location.toLowerCase();
final searchLower = query.toLowerCase();
return nameLower.contains(searchLower);
}).toList();
// when user clear search or remove letters list wont back to it's default
setState(() {
this.query = query;
this.locations = newLocations;
});
}
Any suggestions?
Update
here is my SearchWidget file (just in case)
class SearchWidget extends StatefulWidget {
final String text;
final ValueChanged<String> onChanged;
final String hintText;
const SearchWidget({
Key key,
this.text,
this.onChanged,
this.hintText,
}) : super(key: key);
#override
_SearchWidgetState createState() => _SearchWidgetState();
}
class _SearchWidgetState extends State<SearchWidget> {
final controller = TextEditingController();
#override
Widget build(BuildContext context) {
final styleActive = TextStyle(color: Colors.black);
final styleHint = TextStyle(color: Colors.black54);
final style = widget.text.isEmpty ? styleHint : styleActive;
return Container(
height: 42,
margin: const EdgeInsets.fromLTRB(16, 16, 16, 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
border: Border.all(color: Colors.black26),
),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: TextField(
controller: controller,
decoration: InputDecoration(
icon: Icon(Icons.search, color: style.color),
suffixIcon: widget.text.isNotEmpty
? GestureDetector(
child: Icon(Icons.close, color: style.color),
onTap: () {
controller.clear();
widget.onChanged('');
FocusScope.of(context).requestFocus(FocusNode());
},
)
: null,
hintText: widget.hintText,
hintStyle: style,
border: InputBorder.none,
),
style: style,
onChanged: widget.onChanged,
),
);
}
}
UPDATE 2
I've managed to fix #2 in my logic and get subtitles include search results by following code (clear search issue still remains)
void searchLocation(String query) async {
final newLocations = locations.where((location) {
final nameLower = location.location.toLowerCase();
final countryLower = location.country.toLowerCase(); // added
final searchLower = query.toLowerCase();
return nameLower.contains(searchLower) || countryLower.contains(searchLower); //changed
}).toList();
setState(() {
this.query = query;
this.locations = newLocations;
});
}
Note:
I've accepted Yair Chen answer but I need to make some clarifications to address the issue:
Based on Yair Chen answer I had to create new list List<World> filteredLocations = [];
Then in my ListView.builder I've changed itemCount and child like following:
ListView.builder(
itemCount: filteredLocations.isNotEmpty ? filteredLocations.length : locations.length,
//...
child: filteredLocations.isNotEmpty ? card(... //filteredLocations[index].location//...) : Card(... //locations[index].location// ...),
This way index issue on filtered result solved and card data will get data regarding of the list they are returning from.
You can add another list called filteredLocations and then save to the filtered location the new list. The function .where() changes the original List as well which you dont want. You can shallow copy (using spread operator) the list and then create a new list that matches the query like that:
void searchLocation(String query) async {
// currently only search in titles (need to add subtitle as well)
// [...locations] copies the list to not change original list
final newLocations = [...locations].where((location) {
final nameLower = location.location.toLowerCase();
final searchLower = query.toLowerCase();
// Also adding subtitle check (whether the name contains it or the subtitle does
return nameLower.contains(searchLower) || location.subtitle.toLowerCase().contains(searchLower);
}).toList();
// when user clear search or remove letters list wont back to it's default
setState(() {
this.query = query;
this.filteredLocations = newLocations;
});
}
This way the original list will never be changed and only the filteredList will be updated. It'll also fix the issue that when the string is empty you want all the items to appear.
Good luck and let me know if you need anything else :)

Dart/Flutter: Strings of element inside a List becomes empty when passing as an argument (Why??)

Strings of element inside a List becomes empty when passing as an argument.
It was working before. I don't know what happened that it stopped working, and started passing empty.
I have a model called SubjectiveList, it is the list I am talking about.
class SubjectiveList {
String id;
String name;
List<Item> items;
SubjectiveList({this.id, this.name, this.items});
}
This list has the property items. What becomes empty is the properties inside the Item object.
class Item {
String id;
String name;
Content content;
Item({this.id, this.name, this.content});
}
On the debugger, The newList instance appears fine, with the object names (ps: the ID is okay to be null at this point because it will come from Firestore Database later)
Here is the code with the screenshots:
Future<dynamic> showListInfoDialog() {
final userData = Provider.of<UserData>(context, listen: false);
GlobalKey<FormState> _addListInfoFormKey = GlobalKey<FormState>();
final ValueNotifier<int> tabIndex =
Provider.of<ValueNotifier<int>>(context, listen: false);
TempListViewModel tempList =
Provider.of<TempListViewModel>(context, listen: false);
return showDialog(
context: context,
child: SimpleDialog(
title: Text("List Info"),
children: <Widget>[
Padding(
padding: const EdgeInsets.all(defaultSpacing),
child: Form(
key: _addListInfoFormKey,
child: Column(
children: <Widget>[
TextFormField(
onChanged: (val) => tempList.setListName(val),
validator: (val) => val.isEmpty ? 'Write a name' : null,
decoration: InputDecoration(
prefixIcon: Icon(Icons.featured_play_list),
labelText: "List Name",
),
),
SizedBox(height: defaultSpacing),
SizedBox(
width: double.infinity,
child: RaisedButton(
child: Text("Create List"),
color: successColor,
onPressed: () {
if (_addListInfoFormKey.currentState.validate()) {
final newList = SubjectiveList(
name: tempList.list.name,
items: tempList.list.items);
DatabaseService(uid: userData.uid)
.addListToDatabase(newList); // <-- HERE
tempList.init();
tabIndex.value = 0;
Navigator.of(context).pop();
}
},
),
)
],
),
),
),
],
),
);
}
And then it appears empty when coming to the function!!
Future addListToDatabase(SubjectiveList list) async { <-- HERE
DocumentReference listDocument =
await userDocument.collection('lists').add({'name': list.name});
[...]
}
Thanks #edenar
Now I understand what happened. In Flutter the line "final newList = SubjectiveList(name: tempList.list.name, items: tempList.list.items);" makes a pointer reference, and not an declaration of the current value. So, when it goes to the next line and executes tempList.init() it is clearing the list before getting the argument in the function.
So it worked putting await in that line.

Flutter: There should be exactly one item with [DropdownButton]'s value

I am trying to create a dropdown button in Flutter. I am getting a List from my database then I pass the list to my dropdownButton everything works the data is shown as intended but when I choose an element from it I get this error:
There should be exactly one item with [DropdownButton]'s value: Instance of 'Tag'.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 805 pos 15: 'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'
I tried setting DropdownButton value to null it works but then I can't see the chosen element.
Here is my code:
FutureBuilder<List<Tag>>(
future: _tagDatabaseHelper.getTagList(),
builder: (BuildContext context, AsyncSnapshot<List<Tag>> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.height * 0.2,
),
Container(
margin: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.07),
child: Theme(
data: ThemeData(canvasColor: Color(0xFF525A71)),
child: DropdownButton<Tag>(
value: _selectedTag,
isExpanded: true,
icon: Icon(
Icons.arrow_drop_down,
size: 24,
),
hint: Text(
"Select tags",
style: TextStyle(color: Color(0xFF9F9F9F)),
),
onChanged: (value) {
setState(() {
_selectedTag = value;
});
},
items: snapshot.data.map((Tag tag) {
return DropdownMenuItem<Tag>(
value: tag,
child: Text(
tag.tagTitle,
style: TextStyle(color: Colors.white),
),
);
}).toList(),
value: _selectedTag,
),
),
),
I used futureBuilder to get my List from database.
Well, since no problem has an exact same solution. I was facing the same issue with my code. Here is How I fixed this.
CODE of my DropdownButton:
DropdownButton(
items: _salutations
.map((String item) =>
DropdownMenuItem<String>(child: Text(item), value: item))
.toList(),
onChanged: (String value) {
setState(() {
print("previous ${this._salutation}");
print("selected $value");
this._salutation = value;
});
},
value: _salutation,
),
The Error
In the code snippet below, I am setting the state for a selection value, which is of type String. Now problem with my code was the default initialization of this selection value.
Initially, I was initializing the variable _salutation as:
String _salutation = ""; //Notice the empty String.
This was a mistake!
Initial selection should not be null or empty as the error message correctly mentioned.
'items == null || items.isEmpty || value == null ||
And hence the crash:
Solution Initialize the value object with some default value. Please note that the value should be the one of the values contained by your collection. If it is not, then expect a crash.
String _salutation = "Mr."; //This is the selection value. It is also present in my array.
final _salutations = ["Mr.", "Mrs.", "Master", "Mistress"];//This is the array for dropdown
Might also get this error if trying to set value of dropdown with a class instance;
var tag1 = Tag();
var tag2 = Tag();
print(tag1 == tag2); // prints false, dropwdown computes that value is not present among dropdown options
To solve this override operator ==:
class Tag{
String name = "tag";
#override
bool operator ==(Object other) => other is Tag && other.name == name;
#override
int get hashCode => name.hashCode;
}
or use https://pub.dev/packages/equatable lib
class Tag extends Equatable{
String name = "tag";
#override
List<Object> get props => [name];
}
I had the same problem. The solution is simple: you have to be sure that the String that is your default dropdownvalue is contained in the list that you want to use in your dropdownmenu. If you wanted to, let’s say, use a list from an api, you should be sure to know at least one value of that list, so that you could assign it to the variable that is your default dropdownvalue.
Here I want display a list that I obtain from an api. In order to not obtain the error, I set my defaultdropdownvalue with the name ‘Encajes’ that is one of the existing categories that my list contains.
String dropdownValue = "Encajes";
items: categoriesString
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
Code of my dropdown
child: DropdownButton(
items: _currencies.map((String value) {
return DropdownMenuItem<String>(
child: Text(value),
value: value,
);
}).toList(),
value: 'Rupees',
onChanged: (String newValueSelected) {
// Your code to execute, when a menu item is selected from
dropdown
},
))
var _currencies = ['Rupee','Dollar','Pound'];
I faced same error because the value in the dropdown code block is not matching with any of the fields in _currencies
Okay, some of the answers on this thread will definitely help you resolve the issue. But it is important to clarify why this issue occurs in the first place and what the DropdownButton expects from you.
To give you a little background on the issue it is important to understand how two instances of dart objects are compared.
You will very likely not see the above error if your DropdownButton is dealing with a List of int, String, bool, etc.
This is because you can directly compare primitive types and you would get the expected result.
for instance
int x = 5;
int z = 10;
int y = 5;
String foo= 'hello';
String bar = 'hello;
x == z; // false
x == y; // true
foo == bar; // true
But when dealing with Custom Objects you have to be extra careful and you must ensure you override the "==" operator so that dart knows how to compare instances of your custom object. By default, two objects are equal if they are of the same instance.
consider the Tag class,
class Tag{
final String name;
final String code;
Tag({this.name,this.code});
}
final tag1 = Tag(name:'foo', code: 'hello');
final tag2 = Tag(name:'foo', code: 'hello');
Tag tag3 = tag1;
when you compare
tag3==tag1 dart would return true as expected, But when you compare tag1 == tag2, the dart would return false, since both objects are not of the same instance.
So to deal with this issue you need to override the == operator as shown below
class Tag{
final String name;
final String code;
Tag({this.name,this.code});
#override
bool operator ==(Object other){
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is Tag &&
other.name == name &&
other.code == code
}
}
Now when you compare tag1 ==tag2 it would return true.
This is documented in the official docs here https://dart.dev/guides/language/effective-dart/design#equality
Coming to the DropdownButton error it expects
items is not null
items is not empty
value is not null
value must be present only once in items
Point 4 would fail if you are using Custom objects without overriding the == operator and hence you would get the above error.
TLDR;
So to deal with the error, ensure the above 4 points satisfy and override the == operator so that dart can compare instances of your Tag class as you would expect.
just make the tag class extend from Equatable and pass the attributes to the props.. this did the trick for me.
class Tag extends Equatable{
String id;
String name;
Tag(this.id, this.name);
#override
List<Object> get props => [id,name];
}
I have had the same issue and surprisingly, there were duplicates in my list of items which were being fetched from a remote DB.
Each time I fetched the data from the server (when a new app user logged in), the data had no duplicates but the same data was being added to the list multiple times because I was logging in multiple users on the same device. Maybe your bug is something similar.
So, make sure you remove any duplicates in the snapshot.data before setting them as items of the DropDownButton.
i had the same Error and my default value was not present in the listItems was mapping in the Dropdown Button as :
String defaultvalue = 'selectCategorie'
const List<String> Subcategories = ['category 1','category 2','category 3'...];
Had to Change to this :-
String defaultvalue = 'selectCategorie';
const List<String> Subcategories = ['selectCategorie','category 1','category 2','category 3'...];
now when you pass the defaultvalue in the DropdownButton no errors
DropdownButton (
item:[]
onChanged: (String values){
print(values);
setState(() {
defaultValue = values;
});
},
value: defaultValue,
)
I used a trick. The selected item make as first index item in the list .So when changing item at every time remove the item from list and reinsert the item as first item in the list . Please refer the below code. Here iam using Object as the drop down item and the widget i make it as extracted function. and also before calling the dropDownButton function make
//items list like below
List<LeaveType> items = [
(id=1,name="Sick"),
(id=2,name="Paid")
]
selectedLeave = null;
Row leaveTypeDropDown(StateSetter setCustomState, List<LeaveType> items) {
if(selectedLeave != null){
items.remove(selectedLeave);
items.insert(0, selectedLeave);
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children : [
text("Select Leave Type",textSize: 15),
Padding(padding: const EdgeInsets.all(5)),
Expanded(
child: Container(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black,width: 1),
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<LeaveType>(
isExpanded: true,
//initial value
value: selectedLeave != null ? items[0] : null,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
hint: text("Leave Type"),
style: const TextStyle(
color: Colors.black
),
onChanged: (LeaveType value) {
setCustomState(() {
selectedLeave = value;
items.remove(selectedLeave);
items.insert(0, selectedLeave);
});
},
items: items
.map((leave) {
return new DropdownMenuItem<LeaveType>(
value: leave,
child: text(leave.name),
);
}).toList(),
),
),
),
),
]
);
}
I changed as below and it got solved:
Initial Code:
List<GamesModel> users = <GamesModel>[
new GamesModel(1,"Option1"),
new GamesModel(2,"Option2"),
];
return users;
Changed Code:
List<GamesModel> users = <GamesModel>[
const GamesModel(1,"Option1"),
const GamesModel(2,"Option2"),
];
return users;
If anybody want i can put the whole code
Note that if the list has duplicated values, it will also has this error.
For example, if languages = ["English", "English", "French"];
then if I set the default language = "English".
DropdownButton<String>(
value: language,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: AppColors.highLightTextColor),
underline: Container(
height: 1,
color: AppColors.underLineColor,
),
onChanged: (String? newValue) async {
setState(() {
language = newValue;
});
},
items: languages.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
Remove the duplicate values, then it works.
So I found a solution.
I created empty List to hold my Tag objects.
List<Tag> _tagList = [];
Then, in my initState i assigned the list i get from database to the previous List
#override
void initState() {
super.initState();
_tagDatabaseHelper.getTagList().then((foo) {
setState(() {
_tagList = foo;
});
});
}
Finally My DropdownButton code :
DropdownButton<Tag>(
isExpanded: true,
icon: Icon(
Icons.arrow_drop_down,
size: 24,
),
hint: Text(
"Select tags",
style: TextStyle(color: Color(0xFF9F9F9F)),
),
items: _tagList.map((foo) {
return DropdownMenuItem(
value: foo,
child: Text(foo.tagTitle),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedTag = value;
});
},
value: _selectedTag,
),
In my case, i use empty String for default
value : dropdownValue != "" ? dropdownValue : null
Like this, errors be gone
The exact answer is:
keep "value" null before user selection:
String selectedValue = '';
And in the DropdownButton2 Widget:
...
value: selectedValue.isEmpty ? null : selectedValue,
...
It says if selectedValue is empty then give null but when user select a value then give selectedValue
you can avoid the null value using a ternary operator:
Container(
child:
new DropdownButton<String>(
value: dropdownValue ?? "1",
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.black, fontSize: 18),
underline: Container(height: 2, color: Colors.white24, ),
items: <String>['1', '2', '3', '5'].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);}).toList(),
onChanged: (value) {
setState(() { dropdownValue=value;});
},
)),
This error also occurs if you forget to give dropdown menu items a value.
==== WORKS ====
<String>['A', 'B', 'C'].map<DropdownMenuItem<String>>((vehicle) {
print("vehicle is $vehicle");
print("vehicle is equal ${vehicle == x.value}");
return DropdownMenuItem<String>(
value: vehicle,
child: Text(
// vehicle.vehicleInfo!.vehicleType!,
vehicle,
style: TextStyle(
color: Colors.grey[600],
),
),
);
}).toList(),
==== DOESNT WORK ====
<String>['A', 'B', 'C'].map<DropdownMenuItem<String>>((vehicle) {
return DropdownMenuItem<String>(
child: Text(
vehicle,
style: TextStyle(
color: Colors.grey[600],
),
),
);
}).toList(),
DropdownButton<String>(
iconEnabledColor: Colors.cyan.withOpacity(.6),
isExpanded: true,
itemHeight: 50,
iconSize: 30,
hint: Text("Choose Province"),
items: _provinces
.map((e) => DropdownMenuItem(
child: Text(e),
value: e,
))
.toList(),
value: _Province,
onChanged: (String? value) async{
final respnose=await FirebaseFirestore.instance.collection('city').where('provinceName',isEqualTo: value).get();
_city=[];
for(var item in respnose.docs){
print(item.data());
_city.add(item.data()['name']);
}
print(_Province);
setState(() {
_city=_city;
_Province = value;
});
},
),
SizedBox(height: 20,),
DropdownButton<String>(
iconEnabledColor: Colors.cyan.withOpacity(.6),
isExpanded: true,
itemHeight: 50,
iconSize: 30,
hint: Text("Choose City"),
items:_city
.map((e) => DropdownMenuItem(
child: Text(e),
value: e,
))
.toList(),
value: _City,
onChanged: (String? value) async{
setState(() {
_town=[];
_Town=null;
});
print(_town);
final respnose=await FirebaseFirestore.instance.collection('town').where('cityName',isEqualTo: value).get();
print(respnose.docs);
for(var item in respnose.docs){
print(item.data());
_town.add(item.data()['name']);
}
print(_town);
print(_City);
setState(() {
_City = value;
_town=_town;
});
},
),
SizedBox(height: 20,),
if(true)
DropdownButton<String>(
iconEnabledColor: Colors.cyan.withOpacity(.6),
isExpanded: true,
itemHeight: 50,
iconSize: 30,
hint: Text("Choose Town"),
items:_town
.map((e) => DropdownMenuItem(
child: Text(e),
value: e,
)
)
.toList(),
value: _Town,
onChanged: (String? value)async {
print(_Town);
setState(() {
_Town = value;
});
I had the same problem, and the solution is to fill the value of DropdownButton(value: (use a value from the items you set)
you can not use any value you want, but it should be one of the items that you set for the DropdownMenuItem.
I think because of the update in the framework, the error came out
Here is how you can solve it
DropdownButton(
hint: const Text("Please choose your gender"),
items: <String>["Male", "Female", "Rather not say"]
.map<DropdownMenuItem<String>>((e) {
return DropdownMenuItem<String>(
value: e, child: Text(e.toString()));
}).toList(),
onChanged: (String? value) {
setState(() {
dropdownValue = value!;
});
});
Note that: dropdownValue is a string variable defined at the top
If you are loading the list from an api that returns list, look at what i did to debug the error.
Created a reusable widget that handle future response
Widget rangeLists(selectedValue) {
return FutureBuilder(
future: YourFuture,//this should return Future<List>
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading...');
} else {
List<DropdownMenuItem<String>> categoriesItems = [
DropdownMenuItem(
child: Text(selectedValue),
value: selectedValue,
),
];
print('categoriesItems.last.value');
print(categoriesItems.last.value);
var snapshotAsMap = snapshot.data as List;
for (int i = 0; i < snapshotAsMap.length; i++) {
if (snapshotAsMap[i]['category'] != selectedValue) {
categoriesItems.add(
DropdownMenuItem(
child: Text(snapshotAsMap[i]['category']),
value: snapshotAsMap[i]['category'],
),
);
}
}
return Padding(
padding: const EdgeInsets.only(left: 18.0, right: 18, top: 10),
child: Container(
padding: EdgeInsets.only(left: 25, right: 25),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1),
borderRadius: BorderRadius.circular(25)),
child: DropdownButton<String>(
items: categoriesItems,
icon: const Icon(
Icons.expand_more,
color: Colors.grey,
),
iconSize: 24,
elevation: 16,
isExpanded: true,
style: const TextStyle(color: Colors.grey),
underline: SizedBox(),
onChanged: (value) {
setState(() {
widget.selectedValue = value;
});
},
value: selectedValue,
hint: Text('My courses'),
),
),
);
}
})};
2.Usage
you can called it like this
String selectedValue="Select Here"
rangeLists(selectedValue)//call this as a widget in ur ui
It will handle all list from the Api backend when u return a list u don't need to worry about the error any more
child: DropdownButtonFormField<String>(
hint: Text(widget.hintText == "Select value..."
? "Select ${widget.caption}"
: widget.hintText),
items: getItems(),
value: **checkValue(widget.currentValue)**,
iconSize: 30,
onChanged: widget.onChanged,
),
String? **checkValue(String? value)** {
var arrRet = widget.items.where(
(item) => item[widget.valueMember].toString() == value.toString());
if (arrRet.isEmpty && widget.items.isNotEmpty)
return widget.items[0][widget.valueMember].toString();
return value;
}

Dynamically generate widgets in Flutter

I am trying to dynamically generate a set of widgets based on a particular condition. In this case I am trying to generate a list of RadioTiles
This is how I am trying to generate
List _listings = new List();
Widget _getListings() {
// TODO this will accept json objects in order to display the data
List listings = new List();
int i = 0;
for (i = 0; i < 5; i++) {
listings.add(
new RadioListTile<SingingCharacter>(
title: const Text('Lafayette'),
value: SingingCharacter.lafayette,
groupValue: _character,
onChanged: (SingingCharacter value) {
setState(() {
_character = value;
});
},
),
);
}
// return listings;
}
and I am trying to display this within a stateful widget like this :
return new SafeArea(
child: Column(children: <Widget>[
new Padding(
padding: const EdgeInsets.all(20.0),
child: new Text(
"Verify and Select a Single Listing?",
style: _textStyle,
),
),
ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
_getListings(),
],
),
]));
The issue is that the value of listings is null due to which I am unable to display any widgets on the screen.
Any insights would be useful.
Thanks,
Edit :
If I do try to return a list this is what I see:
I am not sure if this is the best way to dynamically create widgets.
Here are some updates to your code:
Widget build(BuildContext context) {
return Scaffold(body: SafeArea(
child: Container(child: Column(children: <Widget>[
Padding(
padding: const EdgeInsets.all(20.0),
child: Text("Verify and Select a Single Listing?",),
),
Expanded(child: ListView(
padding: const EdgeInsets.all(20.0),
children: _getListings(), // <<<<< Note this change for the return type
),
)
])
)));
}
List _listings = new List();
List<Widget> _getListings() { // <<<<< Note this change for the return type
List listings = List<Widget>();
int i = 0;
for (i = 0; i < 5; i++) {
listings.add(
RadioListTile<String>(
title: const Text('Lafayette'),
value: "c",
groupValue: "x",
onChanged: (_) {
},
),
);
}
return listings;
}
Some things to consider above:
I've made changes to make the code in order to compile and be used for this answer.
added comments for notable changes
List _listings is unused
you can also drop the new keyword when creating new objects (the new version of dart is able to handle this)
Result:
Some comments on the previous answer;
Please do not use unnecessary Containers, if a Container only has a child and nothing else, remove it.
The new keyword does not have to be used, Dart linters even tell not to use it. Like here..
Also if your list does not change you could use a List.unmodifiable like in the example below.
final List<Widget> widgets = List.unmodifiable(() sync* {
for (int i = 0; i < 5; i++) {
yield RadioListTile<String>(
title: const Text('Lafayette'),
value: "c",
groupValue: "x",
onChanged: (_) {
},
);
}
}());
This can be used to avoid unnecessary for loop. Doing the same thing in 2 lines
int numberOfWidgets = 5;
List<Widget> listings = List<Widget>.filled(numberOfWidgets, buildWidget());
This will make a list with exact number of widgets.
Also, this is only helpful if you want similar type of widget in a list