add separate values to DropDownButton - flutter

I have a widget with a item builder. In the item builder I want to create Drop down for each item.
For the value property of this DropdownMenuItem i use
SaleRef _selectedRef;
The problem is when I declare this SaleRef _selectedRef; inside the itemBuilder the value does not change after selecting a item.
When I declare it outside this widget but in the class it changes the value of every dropdown
What can I do to select separate values on every drop down ?
This is the code for creating the items
ListView.separated(
itemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
height: 40,
width: MediaQuery.of(context).size.width * 1 / 5,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.circular(25.0),
border: Border.all(
color: Colors.grey,
style: BorderStyle.solid,
width: 0.80),
),
child: DropdownButton<SaleRef>(
hint: Text(" Select Ref"),
value: _selectedRef,
onChanged: (SaleRef value) {
setState(() {
_selectedRef = value;
print(_selectedRef.refID);
});
},
items: _filteredSaleRef.map((SaleRef ref) {
return DropdownMenuItem<SaleRef>(
value: ref,
child: Row(
children: <Widget>[
Text(
" " + ref.refName,
style: TextStyle(color: Colors.black),
),
],
),
);
}).toList(),
),
),
],
);
},
separatorBuilder: (context, index) {
return Divider(height: 16);
},
itemCount: filteredShopItem.length,
)
When I declare it like this
class _AssignRefPageState extends State<AssignRefPage>
with {
SaleRef _selectedRef;
This happens after selecting a value
When I declare it like this inside the builder like this
itemBuilder: (context, index) {
SaleRef _selectedRef;
return Row(
This is what I get it's always the hint even after I select a one

The problem is you are using the same variable for every dropdownbutton and when you try to declare it in the ListView itemBuilder and you call setState the whole Widget is built setting it to null again
What you can do is create a List of values(the size should be number of dropdownbuttons you have)
Like this
class _AssignRefPageState extends State<AssignRefPage>
with {
List<SaleRef>_selectedRef = List.generate(numberOfDropDowns, (index) => SaleRef());
Then use the array in your setState
DropdownButton<SaleRef>(
hint: Text(" Select Ref"),
value: _selectedRef,
onChanged: (SaleRef value) {
setState(() {
_selectedRef[index] = value;
print(_selectedRef[index].refID); //<- The index is from itemBuilder
});
}

As #Josteve-Adekanbi said the problem was using the same variable
Creating an array with a size outside the class helped me solve my problem
List<SaleRef> _selectedRef = new List(filteredShopItem.length);
Then I used it like this
DropdownButton<SaleRef>(
hint: Text(" Select Ref"),
value: _selectedRef[index],
onChanged: (SaleRef value) {
setState(() {
_selectedRef[index] = value;
print(_selectedRef[index].refID);
});
}

Related

How to validate a form that has cards to select from and the user should obligatorily select one of them?

I have a code whereby the genders of a pet is listed in two separate cards and when the user taps on one of them, it changes color to indicate that it has been selected and is saved in the database. However, the app is letting the user continue to the next page without choosing any one of the values. I want to do a validation whereby the user will have to choose one of the cards to be able to move forward. How can I do this please?
Here is my code:
Expanded(
child: GridView.count(
crossAxisCount: 2,
primary: false,
scrollDirection: Axis.vertical,
children: List.generate(petGenders.length, (index) {
return GestureDetector(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
color:
selectedIndex == index ? primaryColor : null,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
petGenders[petKeys[index]],
SizedBox(
height: 15.0,
),
Text(
petKeys[index],
style: TextStyle(
color: selectedIndex == index
? Colors.white
: null,
fontSize: 18.0,
fontWeight: FontWeight.w600),
),
],
),
),
),
onTap: () {
setState(() {
widget.pet.gender = petKeys[index];
selectedIndex = index;
});
});
}),
),
),
The model:
Map<String, Image> genders() => {
"Male":
Image(image: AssetImage('Assets/images/male.png'), width: 50),
"Female":
Image(image: AssetImage('Assets/images/female.png'), width: 50)
};
Take one variable
bool isGenderSelected = false;
Then change its value to true on tap of card like
onTap: () {
setState(() {
isGenderSelected = true;
widget.pet.gender = petKeys[index];
selectedIndex = index;
});
});
Now check if it's true then only allow the user to go next page or show some message to the user
Scenario like this, I prefer using nullable selectedValue. In this case, I will create nullable int to hold and switch between selection.
int? selectedIndex;
And using color will be like
color: selectedIndex==index? SelectedColor:null,
you can replace null with inactive color.
For validation part, do null check on selectedIndex .
if(selectedIndex!=null){.....}

Flutter DropDownMenu Button update an existing record

I have been experimenting with my flutter drop down button.
Context of what I am doing.
I have an app that will create a job and give it to an available staff member. I have stored all my staff members in a list for the menu button. I will put the code below to show the creation of the job ticket drop down button. selectedTech is at the top of the program so that's not the issue
String selectedTech = "";
Container(
// margin: EdgeInsets.only(right: 20),
width: MediaQuery.of(context).size.width / 2.5,
child: DropdownButton(
hint: Text(
selectedTech,
style: TextStyle(color: Colors.blue),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(color: Colors.blue),
items: listStaffUsers.map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
selectedTech = val.toString();
},
);
},
),
),
The above code works perfect.
However when I want to update the job ticket to change the available staff member I want to set the initial value of the drop down menu to the staff member assigned to the job, because it isn't always guaranteed that they change the staff member allocated to the job. When I set the selected value to my initial value I am locked with that value and cannot change it.
Here is the code I am using to update the staff member.
String selectedTech = "";
int the build method I add
selectedTech = widget.staff;
Container(
// margin: EdgeInsets.only(right: 20),
width: MediaQuery.of(context).size.width / 2.5,
child: DropdownButton(
hint: Text(
selectedTech,
style: TextStyle(color: Colors.blue),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(color: Colors.blue),
items: listStaffUsers.map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
selectedTech = val.toString();
},
);
},
),
),
Any Guidance or examples will be greatly appreciated.
As I understand under the Widget build method you set
selectedTech = widget.staff and then return the widget like this:
Widget build(BuildContext context) {
selectedTech = widget.staff;
return Container( ...
This will systematically lock your selectedTech to widget.staff whenever the build method is called (when you call setState). I mean whenever you change the value of the dropdown, the value will not be set the actual value on the dropdown menu. Because you call setState, setState builds the widget from scratch and selectedTech = widget.staff is called in these steps.
Instead of in build method you should initialize it first, then continue to build method.
class _StaffHomeState extends State<StaffHome> {
String? selectedTech;
// Write a function to initialize the value of selectedTech
void initializeSelectedTech () {
selectedTech = widget.staff;
}
// Call this function in initState to initialize the value
#override
void initState() {
initializeSelectedTech();
super.initState();
}
// Then Widget build method
Widget build(BuildContext context) {
return Container( .....
By this way, you initialize first the value before build method and whenever state changes, the data will be persisted.
I hope it is helpful.

Dropdown list not showing selected value

I have a dropdown list that gets its value from firebase and when I'm trying to pick a value, it shows me an error.
There should be exactly one item with [DropdownButton]'s value: vff.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 850 pos 15: 'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'
here's the source code, I was adding values to dropdown list by using loop, and giving value by name of query
return StreamBuilder<List<CoursesRecord>>(
stream: queryCourseRecord(
queryBuilder: (courseRecord) => courseRecord.orderBy(/*widget.orderBy*/'${widget.orderBy}', descending: true), limit: 5,
),
builder: (context, snapshot) {
// Customize what your widget looks like when it's loading.
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: FlutterFlowTheme.primaryColor,
),
),
);
}
List<CoursesRecord> listViewCourseRecordList = snapshot.data;
for (int i = 0; i < 2; i++) {
String value = listViewCourseRecordList[i].name;
courseItems.add(
DropdownMenuItem(
child: Container(
width: 316 ,
child: CourseBoxName(coursesRecord: listViewCourseRecordList[i],)),
value: value,
),
);
}
// Customize what your widget looks like with no query results.
if (snapshot.data.isEmpty) {
return Container(
height: 100,
child: Center(
child: Text('No results.'),
),
);
}
return Container(
width: MediaQuery.of(context).size.width,
height: 100,
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton(
value: selected,
isDense: true,
items: courseItems,
hint: Text(
"Выберите курс",
),
onChanged: (newValue) {
setState(() {
selected = newValue;
widget.courseSelected(newValue);
});
},
isExpanded: false,
),
),
),
);
},
);
So what can I do to fix this problem, I have stuck with it already for several hours T_T
You got null value so for check conditions like droupdownvalue ?? "xyz"
So you can see XYZ when you got a null value.
it looks like you keep adding items to a list with every build without clearing it
on first build you add 1,2,3 and the list contains [1,2,3]
on second build you add the same items even though they didn't change, and the list contains [1,2,3,1,2,3] which clearly contains duplicates
remove courseItems field and the for loop, then tweak the button definition like following:
DropdownButton(
value: selected,
isDense: true,
items: [
for (int i = 0; i < 2; i++)
DropdownMenuItem(
child: Container(
width: 316,
child: CourseBoxName(
coursesRecord: listViewCourseRecordList[i],
)),
value: value,
)
],
hint: Text(
"Выберите курс",
),
onChanged: (newValue) {
setState(() {
selected = newValue;
widget.courseSelected(newValue);
});
},
isExpanded: false,
)

Flutter did future widget didnt update screen ? i need to update data when its updated

I have an array which i set as a class like this
class FilterArray {
static var FilterArrayData = [];
}
I am simply adding the values in an array. Issue is i am calling this array in a page when array is null. Then on next Page i am adding values in array. Now issue is when i come back in previous page the array is still null. I need to refresh page for this. Which i dont want thats why i use FutureWidget i though from Future widget when array update it will also update in my screen but thats not working. Need to know what can i do for this here i need to update data when array is update so it can show in a Future Widget.
This is my total code
class _SearchPgState extends State<SearchPg> {
Future getData() async {
var result = FilterArray.FilterArrayData;
if (result.length != 0) {
return result;
} else {
return null;
}
}
#override
Widget build(BuildContext context) {
print(FilterArray.FilterArrayData);
return Scaffold(
appBar: AppBar(
title: Container(
height: 50.0,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 3.0),
child: Center(
child: TextField(
onTap: () => Get.to(SearchPgExtra()),
readOnly: true,
decoration: InputDecoration(
hintText: tr('search.search'),
alignLabelWithHint: true,
hintStyle: Theme.of(context).textTheme.subtitle2,
prefixIcon: Icon(Icons.search),
),
),
),
),
),
actions: [
IconButton(
icon: Icon(
FlutterIcons.sort_descending_mco,
color: Theme.of(context).accentColor,
),
onPressed: navigateToSortPage,
),
IconButton(
icon: Icon(
FlutterIcons.filter_fea,
color: Theme.of(context).primaryColor,
),
onPressed: navigateToFilterPage,
),
],
),
body: FutureBuilder(
future: getData(), // async work
builder: (context, projectSnap) {
print(projectSnap.data);
if (projectSnap.hasData) {
return StaggeredGridView.countBuilder(
itemCount: projectSnap.data.length,
crossAxisCount: 4,
staggeredTileBuilder: (int index) => StaggeredTile.fit(2),
mainAxisSpacing: 15.0,
crossAxisSpacing: 15.0,
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: ScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 18.0),
itemBuilder: (context, index) {
var product = projectSnap.data[0][index];
return FadeInAnimation(
index,
child: ProductCard2(
product: product,
isHorizontalList: false,
),
);
},
);
} else {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/images/search.png',
width: MediaQuery.of(context).size.width / 2,
),
SizedBox(height: 15.0),
Text(
'search.title',
style: Theme.of(context).textTheme.headline1,
).tr(),
SizedBox(height: 15.0),
Text(
'search.subtitle',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.subtitle1,
).tr(),
SizedBox(
height: MediaQuery.of(context).size.height / 5,
),
],
),
);
}
},
),
);
}
}
In start array is null then ill add values in array then comeback nothing change then i reload the screen then its working fine.
This is the how i am adding array
RangeSlider(
values: _currentRangeValues,
min: 0,
max: 10000,
divisions: 10,
labels: RangeLabels(
_currentRangeValues.start.round().toString(),
_currentRangeValues.end.round().toString(),
),
onChanged: (RangeValues values) {
setState(() {
_currentRangeValues = values;
//print(_currentRangeValues);
});
var data = searchArray.searchArrayData;
for (int i = 0; i < data.length; i++) {
var current = data[i];
if(current['Price'] >= _currentRangeValues.start && current['Price'] <= _currentRangeValues.end){
print(data);
FilterArray.FilterArrayData.add(data);
}
}
},
),
when data add to FilterArrayData ill go back on Page array on that page not updating so then i change the page and comeback again in SearchPg then i can see data
Don't do your validation with the length of your array. It is like trying to do a validation with something that doesn't existe yet. Instead of that, try using
if(snapshot.hasData)
{ return ... ; }
then, after that, now you can do another validation, for instance, sometimes what you receive is data, but an empty array. There is where I would place the other two options. Remember, inside of the first if.
if(array.isNotEmpty)
{ return ... ; }
and
else
{ return ... ; }
After the first if, then you can now also validate, what will happen if you didn't receive data at all. Simply with an else.
else
{ return ... ; }
In summary: use one first validation with hasData and then, inside of that, decide what to do with the received information. Outside all that, decide what to do if you didn't receive any information at all.
Such cases are faced by new developers often. The best way to deal with it is state management packages like Provider, Bloc, etc. Visit the link and you will find all the relevant packages. Personally, I have used Provider a lot. Bloc is also a good option. A lot of people use it. But I haven't had the chance to use it. Riverpod is an up and coming package. But it still requires a lot of fixing.

How to use ListViewBuilder with switch button for dynamic JSON data in Flutter?

Problem: Using the static Json I can easily set a certain number of switch buttons parallel to the ListView, but when the JSON data is dynamic in nature, I cannot understand how to use the switch button parallel to the ListView Builder?
Example
Input
MapIDs={upi282:1,upi219:1,upi211:0,upi236:0};// number of upi..'s increase or decrease based on fetching data from API
I want to use above Json for ListViewBuilder with switch button so that user can change key-value
And when click on the update button, it gets the updated JSON for the API.
Does anyone know how to implement it?
Code:
MapIDs={upi282:1,upi219:1,upi211:0,upi236:0};// number of upi..'s increase or decrease
bool switch_Value=true;
Widget generateListandGetValue( MapIDs,int LengthofMap,switch_Value) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount:LengthofMap,
itemBuilder: (BuildContext context, int index) {
return Row(
children: <Widget>[
Expanded(
child: ListTile(
title: Text(
'\nName- ${MapIDs[index]}\n',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.w700),
),
trailing:
Switch(
value: switch_Value,
onChanged: AlertsStatus,
activeColor: greenColor,
activeTrackColor: greenColor,
),
// onExpansionChanged: toggleAlertsStatus,
selected: true,
),
),
],
);
});
}
void AlertsStatus(alertsOn) {
setState(() {
print(alertsOn);
alertsOn ? _buttonStatus = 1 : _buttonStatus = 0;
});
}
I tried but could not understand how to use the switch button for dynamic JSON and return value
output: {upi282:0,upi219:0,upi211:1,upi236:0}