Dart: Convert map (Key / Value) into an Ordered List[] - flutter

I'm receiving a document snapshot from firestore and I convert it into a Map<String, dynamic> and I know that maps are not ordered, however, I want to order the layout of my screen fields in a certain order. Problem is, the order that I want to achieve is not alphabetical. Here is a sample of my map:
Map<String, dynamic> customerInfo = {
'projectName': 'John Doe',
'state': 'Arizona',
'city': 'Tempe',
'estimate': 123000,
'geoLocation': '12.345678,23.456789'
}
So, I am sending this map to a loop to iterate over keys/values and converting them into a list of widgets, however, for some screens I need to have 'State' be the first widget, and 'City' be the second widget, and vice versa for other screens. Some maps will have more fields, those extra fields will not matter later on, I just want to maintain a certain order for certain fields if they exist.
I tried looping over the keys using a switch / if to match the keys and do a List<Widget>().add(Text(key, value)) however, the generated list is still unordered obviously. Here is a sample code of what I am trying to achieve:
class ProjectViews {
List<Widget> projectDetailedView(Map<String, dynamic> data) {
final List<Widget> fields = [];
fields.add(
Text(
data['projectName'],
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: constants.primaryTextColor,
),
textAlign: TextAlign.start,
),
);
data.forEach((key, value) {
if (key == 'state') {
fields.add(dataField(key, value));
} else if (key == 'city') {
fields.add(dataField(key, value));
} else if (key == 'estimate') {
fields.add(dataField(key, value));
}
// switch (key) {
// case 'state':
// fields.add(dataField(key, value));
// break;
// case 'city':
// fields.add(dataField(key, value));
// break;
// case 'estimate':
// fields.add(dataField(key, value));
// break;
// }
});
return fields;
}
Widget dataField(String key, dynamic value) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
key,
style: TextStyle(fontSize: 20),
),
Text(
value.toString(),
style: TextStyle(fontSize: 20),
),
],
),
);
}
}
I want to make sure the List<Widget> fields are added in a certain order. The received map may contain 5 elements or 20 elements, only some of those need to be in order.
Can I use a List<String> arranged as my preferred order and convert the map based on this list?
Any other options to achieve this please?
Thanks

So here is my solution to the problem, basically you create a new ordered map with only the field names that you want to display in the order of display, then you populate this new map with the data/widgets.
class ProjectViews {
List<Widget> projectDetailedView2(Map<String, dynamic> data) {
final List<Widget> fields = [];
final Map<String, dynamic> orderedFields = {
'state': '',
'city': '',
'geoLocation': '',
}; // this map has the items we want to display in the order we want
fields.add(
Text(
data['projectName'],
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: constants.primaryTextColor,
),
textAlign: TextAlign.start,
),
);
data.forEach((key, value) {
if (orderedFields.keys.contains(key)) {
orderedFields[key] = dataField(key, value);
}
});
orderedFields.forEach((key, value) {
if (value.runtimeType == Widget) {
fields.add(value);
}
});
return fields;
}

Maybe you can use a LinkedHashMap. It keeps the key insertion order.

FirebaseFirestore has a .orderBy() function that you can use to order your data when you query a collection.
QuerySnapshot<Map<String, dynamic>> snapshot =
await firestoreInstance.collection('collectionName')
.orderBy('value', descending: false)
.get();
Note: Your question seems like you're trying to get a list of Customer Info from firebase, so all you have to do is replace the 'collectionName' in my code with the name of the customer info collection. This is a querysnapshot because it queries the collection and gets you all the customer info in the collection.
You will also have to replace the 'Value' in my code here with the key of the value you want it to be ordered with. You can also choose whether it should be ascending or descending by setting the descending value to either true or false.
I hope this helps.

Related

How Should I Deal with Null Values in a StreamBuilder?

I'm trying to build a flutter view that loads a list of items ('cost codes' in the code snippet) from a database call. This code works elsewhere in my project where I already have data in the database, but it fails when it tries to read data from an empty node. I can provide dummy data or sample data for my users on first run, but they might delete the data before adding their own, which would cause the app to crash the next time this view loads.
What's the proper way to deal with a potentially empty list in a StreamBuilder?
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: dbPathRef.onValue,
builder: (context, snapshot) {
final costCodes = <CostCode>[];
if (!snapshot.hasData) {
return Center(
child: Column(
children: const [
Text(
'No Data',
style: TextStyle(
color: Colors.white,
),
)
],
),
);
} else {
final costCodeData =
// code fails on the following line with the error
// 'type "Null" is not a subtype of type "Map<Object?, dynamic>" in type cast'
(snapshot.data!).snapshot.value as Map<Object?, dynamic>;
costCodeData.forEach(
(key, value) {
final dataLast = Map<String, dynamic>.from(value);
final account = CostCode(
id: dataLast['id'],
name: dataLast['name'],
);
costCodes.add(account);
},
);
return ListView.builder(
shrinkWrap: false,
itemCount: costCodes.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
costCodes[index].name,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
costCodes[index].id,
style: const TextStyle(color: Colors.white),
),
);
},
);
}
},
);
}
Personally I tend to avoid handling raw data from a database in the UI code and handle all of this in a repository/bloc layer.
However, to solve your issue you can simply add a ? to the end of the cast like so:
final costCodeData = (snapshot.data!).snapshot.value as Map<Object?, dynamic>?;
You will no longer get the cast exception - however you still have to test costCodeData for null.
This block of code may help:
final data = snapshot.data;
final Map<Object?, dynamic>? costCodeData
if (data == null) {
costCodeData = null;
} else {
costCodeData = (snapshot.data!).snapshot.value as Map<Object?, dynamic>?;
}
if (costCodeData == null){
// Show noData
} else {
// Show data
}
final dataLast = Map<String, dynamic>.from(value);
final account = CostCode(
id: dataLast['id'],
name: dataLast['name'],
);
costCodes.add(account);
},
you declaired dataLast with a Map having key as String, but inside the account variable the id and name are not in the string format, keep those inside "" || '' even after modiying these, if you still face other issue try putting question mark at the end of the line
(snapshot.data!).snapshot.value as Map<Object, dynamic>?

InitialValue isn't working properly in Multi-Select package Flutter

so I am using MultiSelectBottomSheetField in this package. I posted on their github as well as an issue but it seems fairly inactive so i came here looking for help.
And I am having some issues with the initialValue parameter for it. So at the moment, I have data saved in firestore as a string but its in the format of a list. And what i was trying to do was get the string data from firestore -> convert to a list with the respective class -> and then show as initial value in the above package/widget. But whats happening is that the initial value isnt showing, even though the value is not empty.
So for context this is how I change to list from firestore string:
List<Skill?> skillList = [];
void changeSkillToList(String? stringList) {
int indexOfOpenBracket = stringList!.indexOf("[");
int indexOfLastBracket = stringList.lastIndexOf("]");
var noBracketString =
stringList.substring(indexOfOpenBracket + 1, indexOfLastBracket);
var list = noBracketString.split(", ");
for (var i = 0; i < list.length; i++) {
skillList.add(Skill(id: 1, name: list[i].toString()));
}
}
this is how i use the acc widget:
final _skillItems =
skill.map((skill) => MultiSelectItem<Skill>(skill, skill.name)).toList();
MultiSelectBottomSheetField<Skill?>(
selectedColor: Color(0xFF5DB075),
selectedItemsTextStyle:
TextStyle(color: Colors.white),
initialChildSize: 0.4,
decoration: BoxDecoration(),
listType: MultiSelectListType.CHIP,
initialValue: skillList,
searchable: true,
items: _skillItems,
buttonText: Text("Select your skills...",
style: GoogleFonts.inter(
color: Color(0xFFBDBDBD),
fontSize: 16)),
onConfirm: (values) {
context
.read(pharmacistSignUpProvider.notifier)
.changeSkillList(values);
},
chipDisplay: MultiSelectChipDisplay(
items: context
.read(pharmacistSignUpProvider.notifier)
.skillList
?.map((e) =>
MultiSelectItem(e, e.toString()))
.toList(),
chipColor: Color(0xFF5DB075),
onTap: (value) {
context
.read(
pharmacistSignUpProvider.notifier)
.skillList
?.remove(value);
return context
.read(
pharmacistSignUpProvider.notifier)
.skillList;
},
textStyle: TextStyle(color: Colors.white),
),
),
And this is my initState:
List<Skill?> skillList = [];
#override
void initState() {
skillList = changeSkillToList(context
.read(pharmacistMainProvider.notifier)
.userDataMap?["knownSkills"]);
print(skillList);
super.initState();
}
If someone could help me out, it would be very appreciated. Let me know if you guys have any questions
Thanks!!
I get some problem and I fix it by adding the == operator to my entity in your case skill
#override
bool operator ==(Object other) {
return other is Skill && this.id == other.id;
}
inside your Skill class

Using Dart / Flutter, How do I add favorites inside a ListViewBuilder?

I'm trying to allow a user to mark an item being built by a ListViewBuilder as a favorite. With my current code, when a user favorites one episode, all episodes are marked as favorite. I would like the user to be able to add each episode individually as a favorite and persist that favorite after a restart. I have the data saved to a firebase database but it seems like this should be handled in the app itself.
What is the best way to do this? Thanks!
Here is my current code:
class Epi {
final String endTime;
final String name;
final String networkName;
final String showName;
final String startTime;
Epi({this.endTime, this.name, this.networkName, this.showName, this.startTime});
factory Epi.fromJson(Map<dynamic, dynamic> parsedJson) {
DateTime endTimeCon = DateTime.parse(parsedJson['endTime']);
String newEndTime = formatDate(endTimeCon, [yyyy, '/', mm, '/', dd, ' ', hh, ':', nn, ':', ss, ' ', am]);
DateTime startTimeCon = DateTime.parse(parsedJson['startTime']);
String newStartTime = formatDate(startTimeCon, [yyyy, '/', mm, '/', dd, ' ', hh, ':', nn, ':', ss, ' ', am]);
return Epi(
endTime: newEndTime,
name: parsedJson['name'],
networkName: parsedJson['networkName'],
showName: parsedJson['showName'],
startTime: newStartTime,
);
}
}
bool _isFavorited = true;
void _toggleFavorite() {
setState(() {
if (_isFavorited) {
_isFavorited = false;
} else {
_isFavorited = true;
}
});
}
body: Column(
children: <Widget>[
SizedBox(height: 5.0),
Expanded(
child: ListView.builder(
itemCount: elist.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
selectEpisode(index);
},
child: Card(
child: Column(
children: <Widget>[
ListTile(
title: Text(elist[index].name),
subtitle: Text(elist[index].startTime),
leading: IconButton(
icon: (_isFavorited ? Icon(Icons.favorite_border) : Icon(Icons.favorite)),
color: Colors.red[500],
onPressed: _toggleFavorite,
),
trailing: Icon(Icons.arrow_forward_ios)
)
],
),
),
);
}),
),
],
)
In my Congress Fahrplan App (Github) I'm doing exactly what you want to achieve.
In favorite_provider I store the value in the object itself and add it to my list of favorited objects. Whenever an object is added to this list, the list is written to the disk as JSON with my file_storage class.
When the app is restarted, the objects are fetched from a REST API. Then I match their IDs with the objects from the local JSON and set whether they are favorited or not to restore the favorite state.
Making a favorite list of items basically differs based on the app design and you might as well develop your own logic for this purpose. Now, while what #benjaminschilling33 posted is true, you can also achieve this in a simple way.
What I would do is, add a boolean called isFavorite on the constructor like this:
class Epi {
final String endTime;
final String name;
final String networkName;
final String showName;
final String startTime;
bool isFavorite;
}
//initialize the isFavorite to false cause no item in your list is is favorite at the beginning
Epi({this.endTime, this.name, this.networkName, this.showName, this.startTime, this.isFavorite=false});
//lets create a list _episode which contains all the movie for demonstration purpose
List<Epi> _episode = [Epi(initialized data)];
//create a getter for the list
List<Epi> get episode{
return _episode.where((Epi episode) => episod.isFavorite).toList(); //this will return a list where the isFavorite is true
}
//You can then set your icon (in the list-tile) based on your isFavorite result
ListTile(
...
icon: Icons(elist[index].isFavorite?Icon(Icons.favorite):Icon(Icons.favorite_border);
)
//Then adjust the logic on your onPress
onPressed: (){
setState((){
elist[index].isFavorite=!elist[index].isFavorite //this will vary from true to false and vice versa when pressed
});
}
This is the simplest way to add list of items that is favorited by the user rather than building another list for the favorite section. What I wrote here is offline based test you can achieve and the key take away is the where property which is:
List<Epi> episode=[some data]
epsode.where((Epi episode)=>episode.isFavorite).toList();
You can use this method even after deploying your app to the cloud database by creating that attribute in your database based on the user's id.

How to retrieve data from Firestore - Flutter

I am trying to retrieve data from my firestore database and assign those values to a List in flutter
But the problem is even though I am able to retrieve the data, I can't assign it to a List
Here is how my data retrieving method:
Stream<List<News>> getNews(){
return _db.collection("news")
.snapshots()
.map((snapshot) => snapshot.documents.map((doc) => News.fromMap(doc.data, doc.documentID)).toList(),);
}
This is where I I try to get this data from the firestore to a List
Widget _showSearchBar(BuildContext context) {
List = FireStoreServiceApi().getNews(); //this produces an error, see below to see the error
List list = [
"Banuka",
"Banuka",
"Banuka",
];
return GFSearchBar(
// overlaySearchListHeight: 160.0,
searchList: list,
searchQueryBuilder: (query, list) {
return list
.where((item) => item.toLowerCase().contains(query.toLowerCase()))
.toList();
},
overlaySearchListItemBuilder: (item) {
return Container(
padding: const EdgeInsets.all(3),
child: Text(
item,
style: const TextStyle(fontSize: 18),
),
);
},
onItemSelected: (item) {},
);
}
But this produces:
A value of type 'Stream>' can't be assigned to a variable of type 'List'.
Try changing the type of the variable, or casting the right-hand type to 'List'
I don't know how to fix this and Can someone please help me?
You need to add type of list like below,
List<News> newsList = FireStoreServiceApi().getNews();

DropdownButton in Flutter not changing values to the selected value

In my code I added a dropdown which looks like below, When I switched the selection in dropdown its not getting updated its showing exception, I have declared a variable in statefull widget , In my dropdown function I am assigning that as A value to the dropdown Button, and in Onchanged I am passing the json to another function there I am taking the value from a variable and assigning it to a opSelected variable inside the setState
class _ReportFilterState extends State<ReportFilter> {
String opSelected;
//declared string to hold the selected value in dropdown within the state class.
buildMainDropdown(List<Map<String, Object>> items, StateSetter setState) {
return Container(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 27.0,
vertical: 16.0,
),
child: Align(
alignment: Alignment.topLeft,
child: DropdownButtonHideUnderline(
child: DropdownButton(
isExpanded: true,
hint: Text("Choose Filters"),
value: opSelected, // Here assigning the value
items: items
.map((json) => DropdownMenuItem(
child: Text(json["displayName"]), value: json))
.toList(),
onChanged: (json) {
manageIntState(json, setState);
},
),
),
),
),
);
}
void manageIntState(Map<String, Object> jsonSelected, StateSetter setState) {
setState(() {
dispName = jsonSelected["displayName"];
//here I am setting the selected value
opSelected = dispName;
//Doing some operations
id = jsonSelected['id'];
type = jsonSelected['type'];
selectedFilterOption = jsonSelected;
if (jsonSelected.containsKey("data")) {
List<Map<String, Object>> tempList;
List<String> dailogContent = List<String>();
tempList = jsonSelected['data'];
tempList
.map((val) => {
dailogContent.add(val['displayId']),
})
.toList();
_showReportDialog(dailogContent);
}
});
}
But when I run I will end up with error
items==null||
items.isEmpty||value==null||itsems.where((DropdownMenuItem
item)=>item.value==value).length==1 is not true ..
Let me know what I have done wrong in code so its giving me like this, if I commented its not showing the selected dropdown value.
That error happens when the selected value of the DropdownButton is not one of the values of it's items.
In your case, your items values are json which is a Map<String, Object>, and the value of the DropdownButton is opSelected which is a String.
So you need to change the type of opSelected like this:
Map<String, Object> opSelected;
Also make sure you are passing a reference of the same list of items to buildMainDropdown(), because if you are creating a new list while calling buildMainDropdown() then the DropdownButton will have another reference of options and it's not allowed
Note: you may want yo use dynamic instead of Object for the Map, like this:
Map<String, dynamic> opSelected;
Here is why: What is the difference between dynamic and Object in dart?