I have a dropdown list populated with users, I want to get the user id and pass to a function whenever a user is selected from the list
An unhandled exception is occurring instead
the exception
E/flutter (28482): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: Null check operator used on a null value
The below snippet is where how I am fetching the users
User? sid;
List<User> users = [];
//fetch users
Future<List<User>>? getUsers() async {
var result = await client.get(usersUrl);
return userFromJson(result.body);
}
Future<void> fetchandShow() async {
final users = await getUsers();
setState(() {
this.users = users ?? [];
});
}
#override
void initState() {
super.initState();
fetchandShow();
}
below is the dropdownbutton where I am displaying the users
DropdownButtonFormField<User>(
hint: Text('Select user'),
decoration: InputDecoration(
border: InputBorder.none,
),
value: sid,
items: users
.map((item) => DropdownMenuItem(
value: item,
child: Text(
item.username,
style: TextStyle(fontSize: 20.0),
),
))
.toList(),
onChanged: (item) => setState(() {
sid!.id = item as String?;
print(sid!.id);
}),
),
below is where i want to pass the user id
ElevatedButton(
onPressed: () async {
await createNote(
_bodyController.text, int.parse(sid!.id.toString()));
Navigator.pop(context);
},
child: Text('submit'),
)
Here "timeZoneType" is the List of data and once the user select any option from the dropdown, we will get the index ("accountIndex") of the item.
Once we get the index of the item, we can just get that index item details by
"timeZoneType[index]"
var detailData = timeZoneType[index]
Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
height: 45,
child: DropdownButtonHideUnderline(
child: Padding(
padding: const EdgeInsets.only(left: 20.0, right: 1),
child: DropdownButton(
hint: Text("Timezone", style: Constants.editTextStyleLight),
value: _currentSelectedValue.value,
items: timeZoneType
.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: Constants.editTextStyle,
),
);
}).toList(),
onChanged: (value) {
setState(() {
print(value);
accountIndex = timeZoneType.indexOf(value.toString());
print(accountIndex);
});
}),
),
),
),
],
)
Your item is of User type, Handle onChanged as below:
onChanged: (item) => setState(() {
sid = item;
}),
Related
I am trying to use the dropdown menu in my flutter app but getting an error.
Here is the code:
List<String> items = ["Item1", "Item2", "Item3", "Item4"];
String selectedItem = "Item1";
DropdownButton<String>(
items: items.map(
(txt) {
return DropdownMenuItem<String>(
child: Text(
"$txt"
),
);
}
).toList(),
value: selectedItem,
)
In some questions, I saw that we have to initially set a variable to the value present inside our list. I have exactly done that but still getting an error.
Error Message:
There should be exactly one item with [DropdownButton]'s value: Item1.
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'
What is the error here?
Kindly comment if more information is needed.
Here an example, the explanation in the code:
class _MyHomePageState extends State<MyHomePage> {
List<String> items = ["Item1", "Item2", "Item3", "Item4"];
String selectedItem = "Item1";
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
Flex(direction: Axis.vertical, children:[
DropdownButton<String>(
value: selectedItem,
onChanged: (_value) { // update the selectedItem value
setState(() {
selectedItem = _value!;
});
},
items: items
.map<DropdownMenuItem<String>>((String _value) => DropdownMenuItem<String>(
value: _value, // add this property an pass the _value to it
child: Text(_value,)
)).toList(),
),
])
],
),
);
}
}
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 backend u don't need to worry about the error any more
List<String> items = ["Item1", "Item2", "Item3", "Item4"];
String selectedItem = "";
DropdownButton<String>(
items: items.map(
(txt) {
return DropdownMenuItem<String>(
child: Text("$txt"),
);
}
).toList(),
value: selectedItem==""null?"":selectedItem,
)
I'm trying to create a dynamic form so I used the idea of using a listview builder to create it. I was able to successfully create it but I faced that I cannot discard changes made to the form by popping it off after editing it. The two textFormField Job name and rate per hour were able to discard changes as they were using onsaved but on the checkbox I can't do that as it has onChanged which wraps setstate to change its state.
You can take a look at the video at this link to see how it functions as of now - https://vimeo.com/523847256
As you can see that it is retaining the data even after popping the page and coming back which I don't want it to. I'm looking for a way to prevent that and make the form the same as before if the user didn't press save.
I have tried to reassign the variables() in onpressed of back button but that didn't work. I also tried push replacement to the same page to reset it but that also didn't work. I think the cuprit here is the sublist and the initialValueTextFormField and initialValueCheckbox which are used declared under ListView.builder but I don't know how to fix that without affecting the dynamic list functionality.
class EditJobPage extends StatefulWidget {
const EditJobPage({Key key, this.job}) : super(key: key);
final Job job;
static Future<void> show(BuildContext context, {Job job}) async {
await Navigator.of(context, rootNavigator: true).pushNamed(
AppRoutes.editJobPage,
arguments: job,
);
}
#override
_EditJobPageState createState() => _EditJobPageState();
}
class _EditJobPageState extends State<EditJobPage> {
final _formKey = GlobalKey<FormState>();
String _name;
int _ratePerHour;
List<dynamic> _subList = [];
Set newSet = Set('', false);
#override
void initState() {
super.initState();
if (widget.job != null) {
_name = widget.job?.name;
_ratePerHour = widget.job?.ratePerHour;
_subList = widget.job?.subList;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text(widget.job == null ? 'New Job' : 'Edit Job'),
leading: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: <Widget>[
FlatButton(
child: const Text(
'Save',
style: TextStyle(fontSize: 18, color: Colors.white),
),
onPressed: () => _submit(),
),
],
),
body: _buildContents(),
backgroundColor: Colors.grey[200],
);
}
Widget _buildContents() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: _buildForm(),
),
),
),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildFormChildren(),
),
);
}
List<Widget> _buildFormChildren() {
print(_subList);
return [
TextFormField(
decoration: const InputDecoration(labelText: 'Job name'),
keyboardAppearance: Brightness.light,
initialValue: _name,
validator: (value) =>
(value ?? '').isNotEmpty ? null : 'Name can\'t be empty',
onChanged: (value) {
setState(() {
_name = value;
});
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Rate per hour'),
keyboardAppearance: Brightness.light,
initialValue: _ratePerHour != null ? '$_ratePerHour' : null,
keyboardType: const TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
onChanged: (value) {
setState(() {
_ratePerHour = int.tryParse(value ?? '') ?? 0;
});
},
),
Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
itemCount: _subList?.length ?? 0,
itemBuilder: (context, index) {
String initialValueTextFormField =
_subList[index].subListTitle.toString();
bool initialValueCheckbox = _subList[index].subListStatus;
return Row(
children: [
Checkbox(
value: initialValueCheckbox,
onChanged: (bool newValue) {
setState(
() {
initialValueCheckbox = newValue;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
},
);
},
),
Expanded(
child: TextFormField(
minLines: 1,
maxLines: 1,
initialValue: initialValueTextFormField,
autofocus: false,
textAlign: TextAlign.left,
onChanged: (title) {
setState(() {
initialValueTextFormField = title;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
});
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
filled: true,
hintText: 'Write sub List here',
),
),
),
],
);
},
),
TextButton(
onPressed: () {
setState(() {
_subList.add(newSet);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.add),
Text('Add Sub Lists'),
],
),
),
],
),
];
}
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
} else {
final database = context.read<FirestoreDatabase>(databaseProvider);
final id = widget.job?.id ?? documentIdFromCurrentDate();
final job = Job(
id: id,
name: _name ?? '',
ratePerHour: _ratePerHour ?? 0,
subList: _subList);
database.setJob(job);
Navigator.of(context).pop();
}
}
}
And this is the link to the full repository of the whole flutter app in case you want to look at any other part:- https://github.com/brightseagit/dynamic_forms . Thank you.
Note - This is the edited code of this repo - https://github.com/bizz84/starter_architecture_flutter_firebase.
When assigning the list we need to use _subList = List.from(widget.job.subList) instead of _subList = widget.job.subList.
Otherwise, the changes made in _subList will also be made in job.subList .
I need to have the list of add able dropdown of states,district. They can be added. I could add the forms but I need to set the district data according to the states.I get the states from the api. When selecting the states from dropdown I get the districts according to states from api.Its working fine with the district dropdown of index 0 but I am getting the error There should be exactly one item with [DropdownButton]'s value: 1. in other index. How can I achieve this. I followed this https://stackoverflow.com/a/63188955/8023701. I have implemented as follows:
Widget _buildStates(BuildContext context, int i) {
return
Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
state,
style: TextStyle(
// fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.red),
),
SizedBox(
width: 20,
),
DropdownButtonFormField(
validator: (value) => validateDrops(value),
isExpanded: true,
hint: Text(state),
value: _selectedState[i],
onChanged: (newValue) {
setState(() {
print("Stae value");
print(newValue);
_selectedState[i]= newValue;
getMyDistricts(newValue, i);
});
},
items: statess.map((Data item) {
return new DropdownMenuItem(
child: new Text(
item.provinceNepali,
),
value: item.id.toString(),
);
}).toList(),
)
],
));
}
District widget:
Widget _buildDistrict(BuildContext context, int i) {
return Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
district,
style: TextStyle(
// fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.red),
),
SizedBox(
width: 20,
),
mydis != null
? DropdownButtonFormField(
validator: (value) => validateDrops(value),
isExpanded: true,
hint: Text(select),
value: _selectedDistrict[i],
onChanged: (newValue) {
setState(() {
_selectedDistrict[i] = newValue;
getMyMuni(newValue, i);
});
},
items: mydis.map((Datas item) {
return new DropdownMenuItem(
child: new Text(
item.municipalityEnglish,
),
value: item.id.toString(),
);
}).toList(),
)
: Container()
],
));
}
I am getting states from api as follows:
void getAvgProvince() async {
setState(() {
_isLoading = true;
});
ProvinceResponse joinResponse = await getProvince();
if (joinResponse != null) {
setState(() {
statess = joinResponse.data;
_isLoading = false;
});
} else {
setState(() {
_isLoading = false;
});
}
}
And district as follows:
void getMyDistricts(newValue, int i) async {
DistrictResponse joinResponse = await getDistrict(newValue);
if (joinResponse != null) {
setState(() {
mydis = joinResponse.data;
_isLoading = false;
});
} else {
setState(() {
_isLoading = false;
});
}
}
The DropdownButton error There should be exactly one item with [DropdownButton]'s value: 1 occurs because the initial selected value of the dropdown is null. What you can do here is add a placeholder value for the dropdown if it's yet to be active. You can follow a similar approach on this answer.
I have this challenge. All I want to do is to display the item whose quantity was updated on the data table. I can display the item and the quantity. I can also reset the quantity but when I tried to click Save so it should populate on the datatable and perhaps make update request latter, it displays this error below:
And this is the List of the Items:
THis is the form that allows me update the quantity:
Also Instead of displaying the data in Listile. I want to display the data in ListBody with Divider but I don't know how to do it. All the methods I've tried its throwing and error; the widget.farmerBvn and widget.dc_dcOid, Username can be replaced with this parameters:
farmerBvn=22499183844 dcOid=11, agentName=johndoh
I've tried but I keep getting this error on this and how to change the view from Listile to maybe ListBody where I can display more attributes. Please can anyone help me I am new to flutter.
Here's the code that helps me display the Items on a List:
//List Start Here
child: ListView(
children: eops.map((e) {
return ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20)),
title: Text(e.itemName),
content: TextField(
controller: quantity,
keyboardType:
TextInputType.number,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(
7),
),
labelText:
'Input Quantity Collected',
hintText:
e.quantityAllocated),
),
actions: <Widget>[
FlatButton(
child: Text('Reset'),
onPressed: () {
setState(() {
quantity.text = '';
});
},
),
FlatButton(
child: Text('Save'),
onPressed: () {
bool neww = true;
for (EopLine n
in selectedEops) {
if (n.oid == e.oid) {
setState(() {
n.quantityCollected =
quantity.text;
});
neww = false;
break;
}
}
if (neww == true) {
setState(() {
selectedEops.add(EopLine(
oid: e.oid,
itemName: e.itemName,
quantityCollected: e
.quantityCollected,
createdBy:
e.createdBy,
createdOn:
DateTime.now()
.toString(),
itemType: e.itemType,
measuringUnit:
e.measuringUnit));
});
neww = false;
}
Navigator.pop(context);
},
),
],
);
});
},
leading: (
FittedBox(
fit: BoxFit.contain,
child:Text(e.itemName),
)
),
title: Text('${e.quantityAllocated}'),
trailing: Text('${e.quantityCollected}'),
);
// separatorBuilder:
// (context, index) {
// return Divider();
// };
}).toList(),
),
// ],
// ),
),
//And it ends here
And this is the datatable I want to populate:
//Table starts here
child: DataTable(
columns: [
DataColumn(
label: Text('S/N'),
),
DataColumn(
label: Text('EOP Items'),
),
DataColumn(
label: Text('Qty Collected'),
),
// Lets add one more column to show a delete button
DataColumn(
label: Text('Update'),
)
],
rows: selectedEops
.map(
(eop) => DataRow(
selected: selectedEops.contains(eop),
cells: [
DataCell(
Text('${eop.oid}'),
onTap: () {
print('Selected ${eop.oid}');
},
),
DataCell(
Text(eop.itemName),
onTap: () {
print(
'Selected ${eop.itemName}');
},
),
DataCell(
Text(eop.quantityCollected ?? 0),
onTap: () {
print(
'Selected ${eop.quantityCollected ?? 0}');
},
),
DataCell(
Text(eop.quantityAllocated.toString() ?? 0),
onTap: () {
print(
'Selected ${eop.quantityAllocated.toString() ?? 0}');
},
showEditIcon: true,
),
]),
)
.toList(),
),
),
),
///Table Ends here
In the arrays of products in this eops afer the map function I can see quantityAllocated shows null but other items are showing. any line below the eops.map(e) this quantityallocated and some other show null while the rest is showing its value.
children: eops.map((e)
this is the function that performs the http request:
Future<EopLine> get_farmer_eop() async {
SharedPreferences localStorage = await SharedPreferences.getInstance();
var userJson = localStorage.getString('loginRes');
user = json.decode(userJson);
print(user['UserName']);
final response = await http.get(
'http://api.ergagro.com:112/GenerateFarmersEop?farmerBvn=${widget.result}&dcOid=${widget.dc_result}&agentName=${user['UserName']}',
headers: _setHeaders());
print('${response.statusCode}popo');
if (response.statusCode == 200 && response.body != null) {
final jsonStatus = jsonDecode(response.body);
maineops = jsonStatus['Eop'];
List<dynamic> EopItems = maineops['EopLines'];
for (var i in EopItems) {
print('${i['Oid'].toString()} eopitemid');
setState(() {
eops.add(EopLine(
oid: i['Oid'],
itemType: i['EopType'].toString(),
itemName: i['ItemName'],
quantityAllocated: i['QuantityAllocated'].toString(),
quantityCollected: i['QuantityCollected'].toString(),
measuringUnit: i['MeasuringUnit'],
));
// r = maineops;
});
}
} else {
Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
backgroundColor: Colors.grey,
),
);
}
throw Exception();
}
_setHeaders() => {
'Content-type': 'application/json',
'Accept': 'application/json',
};
I'm trying to populate a dropdown button in my flutter app with data coming from my restful api. However i get the error above.
Here is my model;
class SavingsCategory extends Equatable{
final int id;
final String name;
SavingsCategory({
#required this.id,
#required this.name
});
#override
List<Object> get props => [name, id];
}
My repository fetching the data
#override
Future<List<SavingsCategory>> getSavingsCategory()
async {
var token = await tokenRepository.getToken();
final response = await http.get(
'$baseUrl/user/savings-category',
headers: {HttpHeaders.authorizationHeader: 'Bearer $token'},
);
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<SavingsCategory> categoryList = data['savingsCategory'].map<SavingsCategory>((json) {
return SavingsCategory.fromJson(json);
}).toList();
return categoryList;
} else {
throw new Exception("Couldn't get any saving categories");
}
}
My bloc code
class SavingsCategoryBloc {
final repository = SavingsRepository();
final _savingsCategories = PublishSubject<List<SavingsCategory>>();
Stream<List<SavingsCategory>> get savingsCategories => _savingsCategories.stream;
fetchSavingsCategories() async {
final categories = await repository.getSavingsCategory();
_savingsCategories.sink.add(categories);
}
dispose(){
_savingsCategories.close();
}
}
Finally my widget
class _StartSavingPageState extends State<StartSavingPage> {
final SavingsCategoryBloc bloc = SavingsCategoryBloc();
#override
void initState() {
bloc.fetchSavingsCategories();
super.initState();
}
#override
Widget build(BuildContext context) {
....
Container(
padding: EdgeInsets.symmetric(
horizontal: 15.0, vertical: 10.0),
child: StreamBuilder<List<SavingsCategory>>(
stream: bloc.savingsCategories,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return DropdownButton<String>(
items: [
DropdownMenuItem<String>(
child: Text('No Savings Category'),
value: '',
),
],
onChanged: (String value) {
setState(() {
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);
}
return DropdownButton(
value: category,
items: snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
}).toList(),
onChanged: (value) {
setState(() {
category = value;
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);
}),
),
}
}
How can i fix this error? I know the data fetching works just fine. I'm definitely missing something in my widget. Any help would be appreciated.
The DropdownButton value must in item values or must be null.
DropdownButton(
value: categoryId,
items: snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
}).toList(),
onChanged: (value) {
setState(() {
categoryId = value;
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);
The mistake you've made is not returning the DropdownMenuItem from the map.
So:
snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
})
should instead be:
snapshot.data.map((category) =>
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
)