Questions on Flutter DropdownButton Value - flutter

Hello I'm new to flutter and I'm trying to create multiple DropdownButtons based on different documents in my Firebase backend. Since there may be multiple DropdownButton (different documents have different number of dropdowns to be created), I wanted to maintain there values with List<String> formInput (formInput[0] maintains the first DropdownButton value etc).
But I keep encountering this error, and came to the conclusion that it is was the formInput[i] fault.
So my question is why can't I use formInput[i] as a value for a Dropdownbutton? Is formInput[i] not considered one item? And is there a better solution for maintaining unknown number of values?
Many Thanks
Code:
class _SubmissionFormState extends State<SubmissionForm> {
List<String> formInput;
var test;
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return FutureBuilder(
future: getStringFromPref('site'),
builder: (context, snapshot) {
if (snapshot.hasData) {
String site = snapshot.data;
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('form')
.where('name', isEqualTo: site)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
int inputSize = snapshot.data.documents.length;
List<Widget> listOfWidget = [];
formInput = new List<String>.filled(inputSize, '-');
print(formInput.length);
for (int i = 0; i < inputSize; i++) {
DocumentSnapshot snap = snapshot.data.documents[i];
List<DropdownMenuItem> options = [];
// adding drop down menu items
for (int j = 0; j < snap['formVal'].length; j++) {
String formVal = snap['formVal'][j];
options.add(DropdownMenuItem(
child: Text(
formVal,
),
value: formVal,
));
print('[inner loop] for loop $i ' + snap['formVal'][j]);
}
// the list of DropdownButtons
listOfWidget.add(DropdownButton(
value: formInput[i],
items: options,
hint: Text(
"-",
),
onChanged: (value) {
setState(() {
formInput[i] = value;
});
},
));
// listOfWidget.add(Text(snap['formTxt']));
}
return Column(
children: listOfWidget,
);
} else {
return CircularProgressIndicator();
}
});
} else {
return CircularProgressIndicator();
}
},
);
}
}
Error:
I/flutter (30860): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (30860): The following assertion was thrown building StreamBuilder<QuerySnapshot>(dirty, state:
I/flutter (30860): _StreamBuilderBaseState<QuerySnapshot, AsyncSnapshot<QuerySnapshot>>#60d57):
I/flutter (30860): There should be exactly one item with [DropdownButton]'s value: -.
I/flutter (30860): Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
I/flutter (30860): 'package:flutter/src/material/dropdown.dart':
I/flutter (30860): Failed assertion: line 839 pos 15: 'items == null || items.isEmpty || value == null ||
I/flutter (30860): items.where((DropdownMenuItem<T> item) {
I/flutter (30860): return item.value == value;
I/flutter (30860): }).length == 1'
I/flutter (30860):

Related

Adding and Removing items from a list using provider flutter

I'm making favorite list which contains the user favorite journeys using provider then I will display these journeys in the favorite journeys screen.
Favorite.dart:
import 'package:flutter/material.dart';
class Favorite extends ChangeNotifier {
final Text date;
final Text time;
final Text source;
final Text destination;
final Text price;
Favorite(this.date, this.time, this.source, this.destination, this.price);
}
class Following extends ChangeNotifier {
List<Favorite> list = [];
add(favorite) {
list.add(favorite);
notifyListeners();
}
remove(favorite) {
list.remove(favorite);
notifyListeners();
}
}
journeys.dart (Which shows all journeys):
FirebaseAnimatedList(
shrinkWrap: true,
query: Consts.journeyRef.child("journeys"),
itemBuilder: (BuildContext context, DataSnapshot snapshot,
Animation animation, int index) {
try {
return Consumer<Following>(
builder:
(BuildContext context, value, child) {
return Dismissible(
key: UniqueKey(),
secondaryBackground: buildSwipeActionRight(),
background: buildSwipeActionLeft(),
child: ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: <Widget>[
eachTile(
following,
Favorite(
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathDateJourney]),
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathTimeJourney]),
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathSourceCity]),
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathDestinationCity]),
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathPriceJourney]),),)
]),
onDismissed: (direction) =>
dismissItem(context, index, direction),
);
},
);
} catch (e) {
customSnackBar(context, e.toString(), 3, Colors.white24, Colors.brown, 17);
return const Text(
"We Can't show you information disabled by the Administrator");
}
}),
eachTile.dart:
ListTile eachTile(following, favorite) {
return ListTile(
leading: Column(
children: [
favorite.date,
const SizedBox(
height: 10,
),
favorite.time,
],
),
title: Row(
children: [
favorite.source,
const SizedBox(
width: 50,
),
favorite.price
],
),
subtitle: favorite.destination,
trailing: IconButton(
icon: following.list.contains(favorite)
? const Icon(Icons.favorite)
: const Icon(Icons.favorite_border_outlined),
onPressed: () {
print(following.list.contains(favorite));
// this print statement always false
if (following.list.contains(favorite)) {
following.remove(favorite);
} else {
following.add(favorite);
}
print(following.list.contains(favorite));
// this print statement always true
print(following.list);
// this print statement print the list and in each time the code execute new instance added to the list
},
),
);
}
This code is working fine as adding the journey to the list but the Problem is that when you click on the favorite icon again the condition
following.list.contains(favorite)
is returning false (Which means this object is not in the list but that's wrong I try to print the list and there is an instance) it seem that the following instance changed but I didn't create any new instance I think it is creating new different instance in each time.
What is the best way to add and remove items to the favorite list using provider?
the output:
I/flutter ( 578): false
I/flutter ( 578): true
I/flutter ( 578): [Instance of 'Favorite']
V/AutofillManager( 578): requestHideFillUi(null): anchor = null
I/flutter ( 578): false
I/flutter ( 578): true
I/flutter ( 578): [Instance of 'Favorite', Instance of 'Favorite']
V/AutofillManager( 578): requestHideFillUi(null): anchor = null
I/flutter ( 578): false
I/flutter ( 578): true
I/flutter ( 578): [Instance of 'Favorite', Instance of 'Favorite', Instance of 'Favorite']
first clean the code here in journey.dart you are using both the methods of provider you can archive this task by
1.you can use consumer widget or provider.of(context) but you are using both ways to call only Following provider
2.in journey.dart if you decided to use consumer then in (BuildContext context, Following value, Widget? child) you can use value and child directly no need to write data types in front eg.. (BuildContext context, value, child)
3.can you display your console output
The problem was that I'm making equal-to operator between two object and it's returning false that is because all objects in the dart language except the primitive data types like string, int, and double... are not equal to each other since no == operator is overridden and set to it, and this include also collections like lists, maps...
the solution is to override the == operator in Favorite class:
class Favorite{
final Text date;
final Text time;
final Text source;
final Text destination;
final Text price;
Favorite(this.date, this.time, this.source, this.destination, this.price);#override
bool operator == (Object other) {
return other is Favorite && date.toString() == other.date.toString() && time.toString() == other.time.toString() && source.toString() == other.source.toString() && destination.toString() == other.destination.toString() && price.toString() == other.price.toString();
}
}
now when I run following.list.contains(favorite) or following.list[0]==favorite in listTile Widget it will return true.
and that's it
Every time you make a change, you should call notifyListeners();. An example Implementation would be:
Inside your Provider Class:
void add(int n) {
myList.add(n);
notifyListeners();
}

Future<DropDownButton> items empty/null/same values | Flutter

During the process of my web-application I want the user to allow to make changes and save them. For that process I'm using SharedPreferences in order to store the changes. I have a list of titles called konzernDataTitle. In general this list is used to display my titles.
In order to change something I edit this list and "upload" it to my SharedPreferences. However, everything works fine but I cant get the new list prefsKonzernTitle into my DropDownButton. For that I'm using a FutureBuilder. The error is quite simple:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
[...]
The following assertion was thrown building FutureBuilder<List<DropdownMenuItem<String>>>(dirty,
state: _FutureBuilderState<List<DropdownMenuItem<String>>>#dcca3):
Assertion failed:
items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1
"There should be exactly one item with [DropdownButton]'s value: MyTitle. \nEither zero or 2 or more
[DropdownMenuItem]s were detected with the same value"
The relevant error-causing widget was:
FutureBuilder<List<DropdownMenuItem<String>>>
FutureBuilder Function:
Future<List<DropdownMenuItem<String>>> getDropDownItems() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool prefsTitleExist;
var newList = List<String>.empty();
if (prefs.containsKey("prefsKonzernTitle")) {
var getPrefList = prefs.getStringList("prefsKonzernTitle");
newList = getPrefList!;
prefsTitleExist = true;
} else {
prefsTitleExist = false;
}
final actualList = prefsTitleExist ? newList : konzernDataTitle;
return actualList.map((data) {
return DropdownMenuItem<String>(
value: data,
child: Text(
data,
),
);
}).toList();
}
FutureBuilder Widget
FutureBuilder<List<DropdownMenuItem<String>>>(
future: getDropDownItems(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
return DropdownButton<String>(
value: dropdownValue,
items: snapshot.data,
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
i = konzernDataTitle.indexOf(newValue);
titleController.text = konzernDataTitle[i];
linkController.text = konzernDataLink[i];
imageController.text = konzernDataImage[i];
colorController.text =
konzernDataColor[i].toString();
});
},
);
}),
I searched the problems inside the lists but all lists are exactly how they have to be.
So maybe you can help me out. Thanks for any kind of help. All questions will be answered in a few minutes.
^Tim
There should be exactly one item with [DropdownButton]'s value: MyTitle.
Either zero or 2 or more
[DropdownMenuItem]s were detected with the same value
The above error denotes dropdownValue does not match with any value available in dropdown menu item list or more than one value present in dropMenuItemList.
For the first case set dropdownValue = null on initialization and for the second case check menu item doesn't have duplication.

Deleting Item from Dropdown in Flutter results in error

I implemented a DropDown which contains a list of items you can delete.
The Dropdown can't be displayed correctly after deleting the item and that causes the error but i don't know how to fix this. Help is highly appriciated!
The DropDown:
The items are a collection of documents queried from firebase.
Deleting the item removes it from firebase but i get the following error:
This is my code:
var selectedStand;
void deleteStand() {
DocumentReference ref = Firestore.instance
.collection('Standnamen')
.document(selectedStand);
ref.delete();
}
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Standnamen').snapshots(),
// ignore: missing_return
builder: (context, snapshot) {
if (!snapshot.hasData) {
Text("Loading");
} else {
List<DropdownMenuItem> standItems = [];
for (int i = 0; i < snapshot.data.documents.length; i++) {
DocumentSnapshot snap = snapshot.data.documents[i];
standItems.add(
DropdownMenuItem(
child: Text(
snap.documentID,
style: TextStyle(color: Colors.blue),
),
value: "${snap.documentID}",
)
);
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownButton(
items: standItems,
onChanged: (standValue) {
setState(() {
selectedStand = standValue;
});
},
value: selectedStand,
isExpanded: false,
hint: new Text(
"Choose stand to delete",
),
),
)
],
);
}
},
),
],
),
);
}
}
Detailed Error:
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building StreamBuilder<QuerySnapshot>(dirty, state: _StreamBuilderBaseState<QuerySnapshot, AsyncSnapshot<QuerySnapshot>>#46c06):
There should be exactly one item with [DropdownButton]'s value: example3.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 827 pos 15: 'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'
The relevant error-causing widget was:
StreamBuilder<QuerySnapshot> file:///Users/darjusch/Developer/flutterProjects/sommerobst_app_beta/lib/screens/admin/admin_create_stand_screen.dart:67:13
When the exception was thrown, this was the stack:
#2 new DropdownButton (package:flutter/src/material/dropdown.dart:827:15)
#3 _AdminCreateStandScreenState.build.<anonymous closure> (package:sommerobst_app_beta/screens/admin/admin_create_stand_screen.dart:92:23)
#4 StreamBuilder.build (package:flutter/src/widgets/async.dart:509:81)
#5 _StreamBuilderBaseState.build (package:flutter/src/widgets/async.dart:127:48)
#6 StatefulElement.build (package:flutter/src/widgets/framework.dart:4619:28)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
W/erobst_app_bet(20965): Reducing the number of considered missed Gc histogram windows from 119 to 100
════════ Exception caught by rendering library ═════════════════════════════════════════════════════
A RenderFlex overflowed by 99569 pixels on the bottom.
EDIT:
I tried your suggestion which sounds very logical but it did not work i still get the same error.
var selectedDoc;
DropdownButton(
items: standItems,
onChanged: (standValue) {
setState(() {
selectedStand = standValue;
selectedDoc = snapshot.data.documents.firstWhere(
(doc) => doc.documentID == selectedStand,
orElse: () => null,
);
});
},
value: selectedDoc?.documentID,
After deletion DropdownButton is given a value(selectedStand) that none of the DropdownMenuItems contain. So, first check if a document exists whose id is selectedStand otherwise set value to null.
// get the document with id as selectedStand. Will be null if it doesn't exist.
var selectedDoc = snapshot.data.documents.firstWhere(
(doc) => doc.documentID == selectedStand,
orElse: () => null,
);
DropdownButton(
// assign selectedDoc's id (same as selectedStand) if exists
// otherwise null
value = selectedDoc?.documentID,
// ...
),
The logic should not be in onChanged but outside of DropdownButton within the StreamBuilder.
selectedDoc = snapshot.data.documents.firstWhere(
(doc) => doc.documentID == selectedStand,
orElse: () => null,
);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownButton(
items: standItems,
onChanged: (standValue) {
setState(() {
selectedStand = standValue;
});
},
value: selectedDoc?.documentID,
isExpanded: false,
hint: new Text(
"Choose stand to delete"
),
),
],
),
Alternatively you could set selectedStand = selectedDoc?.documentID right after finding selectedDoc, so that selectedStand will always have a valid value.
I guess the problem is that you are adding to the same variable without clearing it first which cause multiple element of items to be there with the same names. So I would suggest clear out the items in standItems right before you call the add function to add items outside the for loop.

How do I prevent Flutter FutureBuilder from firing early?

I'm using the following FutureBuilder to handle fetching 'squad' info from a Firebase database, but the Future is saying it's done before I can process all the data form the database:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getUserSquads(),
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (_userSquads == null) {...} else {
print(snapshot.connectionState);
return Text(_userSquads.length.toString());
}
}
},
);
... the following two functions are the functions I need to be completed before the FutureBuilder is done:
Future<void> _getUserSquads() async {
print('1');
final squadIdsResult = await _userSquadsRef.once();
print('2');
if (squadIdsResult.value == null) {
print('3');
return;
}
print('4');
_userSquadIds = squadIdsResult.value;
print('5');
final test = await _test();
print('6');
}
Future<void> _test() {
print('7');
_userSquadIds.forEach((key, value) async {
print('itter');
final result = await _squadsRef.child(key).once();
_userSquads.add(result.value);
print(result.value);
print(_userSquads);
});
print('8');
print('9');
}
The two print statements print(result.value) and print(_useraSquads) don't execute until after the Future's connection state is done:
I/flutter (29217): 2
I/flutter (29217): 4
I/flutter (29217): 5
I/flutter (29217): 7
I/flutter (29217): itter
I/flutter (29217): 8
I/flutter (29217): 9
I/flutter (29217): 6
I/flutter (29217): ConnectionState.done
I/flutter (29217): {squadName: SAFTS}
I/flutter (29217): [{squadName: SAFTS}]
It seems like the problem is in the _test() function, but I've tried a hundred different ways to write this, and I can't figure out how to make sure that the code is done fetching the data from the database in the forEach block before the Future is set to done.
Your _userSquadIds's foreach is creating issue. If you want to make it async the you can use Future.forEach.
Change following code.
_userSquadIds.forEach((key, value) async {
print('itter');
final result = await _squadsRef.child(key).once();
_userSquads.add(result.value);
print(result.value);
print(_userSquads);
});
With Following one.
await Future.forEach(_userSquadIds, (key,value) async {
print('itter');
final result = await _squadsRef.child(key).once();
_userSquads.add(result.value);
print(result.value);
print(_userSquads);
});

RangeError (index): Invalid value: Valid value range is empty: 0 (http request)

i'm a beginner in flutter and
i have a problem with my http request, when i wanna add in my DropdownButton this http request (that i put in the list called deviceGet) or even print it to see it : i have this error :
RangeError(index) : Invalid value : Valid value range is empty : 0
This is all the error message :
Exception caught by widgets library ═══════════════════════════════════
The following RangeError was thrown building Builder:
RangeError (index): Invalid value: Valid value range is empty: 0
The relevant error-causing widget was
MaterialApp
package:chat/main.dart:180
When the exception was thrown, this was the stack
#0 List.[] (dart:core-patch/growable_array.dart:149:60)
#1 _MyListScreenState.initState
package:chat/main.dart:226
#2 StatefulElement._firstBuild
package:flutter/…/widgets/framework.dart:4428
#3 ComponentElement.mount
package:flutter/…/widgets/framework.dart:4274
#4 Element.inflateWidget
package:flutter/…/widgets/framework.dart:3269
...
════════════════════════════════════════════════════════════════════════════════
Reloaded 1 of 591 libraries in 363ms.
flutter: 0
════════ Exception caught by widgets library ═══════════════════════════════════
The following RangeError was thrown building Builder:
RangeError (index): Invalid value: Valid value range is empty: 0
The relevant error-causing widget was
MaterialApp
package:chat/main.dart:180
When the exception was thrown, this was the stack
#0 List.[] (dart:core-patch/growable_array.dart:149:60)
#1 _MyListScreenState.initState
package:chat/main.dart:225
#2 StatefulElement._firstBuild
package:flutter/…/widgets/framework.dart:4428
#3 ComponentElement.mount
package:flutter/…/widgets/framework.dart:4274
#4 Element.inflateWidget
I don't understand this error because i can see my request if i put in the body
ListView.builder(
itemCount: deviceGet.length,
itemBuilder: (context, index) {
return ListTile(
title: Text("Num $index " + device[index].commands[0].id));
title: Text("Num $index " + deviceGet[index].name));
or if i put
Text(deviceGet[0].name)
but with warning
This is my code :
import 'dart:convert';
import 'package:flutter/material.dart';
import 'Get.dart';
import 'Device.dart';
import 'Commands.dart';
import 'deviceData.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
build(context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyListScreen(),
);
}
}
class MyListScreen extends StatefulWidget {
#override
createState() => _MyListScreenState();
}
class _MyListScreenState extends State {
//
List<deviceData> dataDevice = deviceData.getDevicesData();
List<DropdownMenuItem<deviceData>> listDropDevice;
deviceData deviceSelection;
//
var deviceGet = new List<Device>();
GetDevice() {
GET.getDevice().then((response) {
setState(() {
Iterable list = json.decode(response.body);
deviceGet = list.map((model) => Device.fromJson(model)).toList();
});
});
}
void initState() {
super.initState();
GetDevice();
//print(deviceGet[0].name);
// add in the dropDown
for (int i = 0; i < 5;) {
print(i);
dataDevice.add(deviceData("Device" + i.toString()));
dataDevice.add(deviceData(deviceGet[0].name));
i = i + 1;
}
listDropDevice = buildDropdownMenuItems(dataDevice);
deviceSelection = listDropDevice[0].value;
}
List<DropdownMenuItem<deviceData>> buildDropdownMenuItems(List devices) {
List<DropdownMenuItem<deviceData>> items = List();
for (deviceData device1 in devices) {
items.add(
DropdownMenuItem(
value: device1,
child: Text(device1.name),
),
);
}
return items;
}
onChange(deviceData selectionUtilisateur) {
setState(() {
deviceSelection = selectionUtilisateur;
});
}
#override
build(context) {
return Scaffold(
appBar: AppBar(
title: Text("Device List"),
),
body: Column(children: <Widget>[
//Text(deviceGet[0].name),
DropdownButton(
value: deviceSelection,
items: listDropDevice,
onChanged: onChange,
),
]));
}
}
Thank you for you help
GetDevice() is asynchronous method so it takes time to get data from json and in the begging deviceGet is empty, so it gives error of range.
you can make GetDevice() method asynchronous using await and async* and while calling that method use .then method and then access values.
also, make sure that you are getting data in GetDevice metod.
I already had a similar problem.
The Problem is within the for loop.
In place of
for (int i = 0; i < 5;) {
print(i);
dataDevice.add(deviceData("Device" + i.toString()));
dataDevice.add(deviceData(deviceGet[0].name));
i = i + 1;
}
listDropDevice = buildDropdownMenuItems(dataDevice);
deviceSelection = listDropDevice[0].value;
}
change it to
for (int i = 0; i < 5;) {
print(i);
dataDevice.add(deviceData("Device" + i.toString()));
dataDevice.add(deviceData(deviceGet[i].name));
i = i + 1;
}
listDropDevice = buildDropdownMenuItems(dataDevice);
deviceSelection = listDropDevice[i].value;
}