How to retrieve data from Firestore - Flutter - 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();

Related

How to call graphql query X number of times with different variable values in flutter?

I am using graphql_flutter: ^5.1.0 from pub.dev. Its very easy and helpful. I am able to make queries and mutations easily in it. The only problem is now I am in a situation where I am supposed to call a search query again and again everytime the user enters something in search box. The way I am trying is I have created a separate query method for query widget. In that I am passing different variable value. But it doesnt work. And it gives me same result as before. I am just not sure how I make fresh query call with fresh variable values again and again.
My current code:
#override
Widget build(BuildContext context) {
nameSearch = NameSearch(edgesList, (String s) {
print("searched letter $s");
if (s != null && !s.isEmpty) {
allFilmsQuery(value: ++count);
}
});
return Container(
child: GraphQLProvider(
client: GraphqlConfig.initializeClient(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
final result = await showSearch<Edges>(
context: context,
delegate: nameSearch,
);
print("searched result $result");
},
child: Text("Search")),
allPeopleQuery()
],
)),
);
}
Query method that I have created:
Query allPeopleQuery({int value = 1}) {
var map = MyQueries.getMap(value);
print("allFilmsQuery called:$map");
return Query(
options: QueryOptions(
fetchPolicy: FetchPolicy.networkOnly,
document: gql(MyQueries.allPeople),
// this is the query string you just created
variables: map,
pollInterval: const Duration(seconds: 10),
),
builder: (QueryResult result,
{VoidCallback? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return const Center(child: CupertinoActivityIndicator());
}
var response = result.data;
if (response != null) {
print("allpeople result:$count ${result.data}");
AllPeopleResponse allPeopleResponse = AllPeopleResponse.fromJson(
response["allPeople"]);
edgesList = allPeopleResponse.edges!;
if (nameSearch != null) {
nameSearch.updateList(edgesList);
}
return Center(child: Text(result.data.toString()));
}
return const Center(child: CupertinoActivityIndicator());
},
);
}
As you can see the query method is being called once in beginning in build method. And it works perfectly fine. And gives me the result that I need the first time. But on recalling it on every search letter entered its not working. And keeps giving the same result it returned the first time. Can anyone help me on this, how can I make repeated graphql query calls with fresh variable values and get fresh/update results?

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

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.

Delete map in a firestore table

I am having trouble deleting Maps in a data table in Firestore. Indeed, either I delete my entire array, or I receive an error of the type:
flutter: Failed to delete 1: Invalid argument: Instance of '_CompactLinkedHashSet '
I am attaching my classes to you so that you can understand better.Thank you in advance
CLASS Delete_description :
import 'package:cloud_firestore/cloud_firestore.dart';
class DeleteDescription {
final String city;
final String citee;
final int value;
CollectionReference cities = FirebaseFirestore.instance.collection('city');
DeleteDescription(this.city, this.citee, this.value) {
deleteDescription();
}
Future<void> deleteDescription() {
return cities
.doc(city)
.collection("citee")
.doc(citee)
.set({
"Description": FieldValue.arrayRemove([
{0}
])
})
.then((value) => print("$citee Deleted"))
.catchError((error) => print("Failed to delete $value: $error"));
}
}
CLASS READDESCRIPTION:
import 'package:ampc_93/fonction/firebase_crud/delete_description.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class ReadDescription extends StatefulWidget {
final String titreCity;
final String titreCitee;
ReadDescription(this.titreCity, this.titreCitee);
#override
_ReadDescriptionState createState() => _ReadDescriptionState();
}
class _ReadDescriptionState extends State<ReadDescription> {
#override
Widget build(BuildContext context) {
CollectionReference cities = FirebaseFirestore.instance.collection("city");
return FutureBuilder<DocumentSnapshot>(
future: cities
.doc(widget.titreCity)
.collection("citee")
.doc(widget.titreCitee)
.get(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Documents does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data!.data() as Map<String, dynamic>;
if (data["Description"] == null) {
return Text("");
} else {
return ListView.separated(
itemBuilder: (context, index) {
return ListTile(
title: Text(
data["Description"][index]["Identite"],
textAlign: TextAlign.justify,
),
subtitle: Text(
data["Description"][index]["Role"],
textAlign: TextAlign.justify,
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.red),
),
leading: Icon(Icons.person),
trailing: IconButton(
onPressed: () => DeleteDescription(
widget.titreCity, widget.titreCitee, index),
icon: Icon(Icons.delete_forever),
color: Colors.red[300],
));
},
separatorBuilder: (context, index) => Divider(),
itemCount: data["Description"].length);
}
}
return Text("Loading");
},
);
}
}
I specify that in my database, "Description" is an array and that I would therefore like to delete all the elements of "Description" number 0 for example
The FieldValue.arrayRemove you are using didn't work in this way. There are two methods to delete data from firestore list.
First way is pass element (Not it's index) in FieldValue.arrayRemove which you wants to delete.
Second way is get collection from firestore and modify data according to your need and update collection in firestore.
Have a look on below code for more understanding.
import 'package:cloud_firestore/cloud_firestore.dart';
class DeleteDescription {
final String city;
final String citee;
final int value;
CollectionReference cities = FirebaseFirestore.instance.collection('city');
DeleteDescription(this.city, this.citee, this.value) {
deleteDescription();
}
Future<void> deleteDescription() {
final snapshot = await cities.doc(city).collection("citee").doc(citee).get();
/* Get list from firestore */
final list = snapshot["Description"] as List;
/* Remove first or any element and delete from list */
list.removeAt(0);
/* Update same list in firestore*/
await cities
.doc(city)
.collection("citee")
.doc(citee)
.set({"Description": list}).then((value) => print(" Deleted"));
}
}
A helpful way to consider Firestore "Arrays" is that they are ABSOLUTELY NOT ARRAYS - they are ORDERED LISTS (ordered either by the order they were added to the array, or the order they were in in an array passed to the API as an array), and the "number" shown is the order, not an index. The only way to "identify" a single element in a Firestore Array[ordered list] is by it's exact and complete value. It is VERY unfortunate they chose the name "array".
That said, when you read a document, the result presented to your CODE is in the form of an array, and GAINS the ability to refer to an element by index - which is why you have to EITHER:
=> at the backend / API call, specify an element by "value", which in this case is the ENTIRE object on the list
OR
=> at the Client, read the document, delete the desired element either by index or value, then write the entire array back to the backend.

use T.parameter when passing list of class ex:(List<User>) to a method

I am using "search_widget" package in my project in different screens so I built a method that take a list of class:
search<T>(List<T> myList, context) {
return SearchWidget<T>(
dataList: myList,
hideSearchBoxWhenItemSelected: false,
listContainerHeight: MediaQuery.of(context).size.height / 4,
queryBuilder: (query, myList) {
return myList
.where(
(item) => **myList.name**toLowerCase().contains(query.toLowerCase()))
.toList();
},
popupListItemBuilder: (T myList) {
return Container(
padding: const EdgeInsets.all(12),
child: Text(
**myList.name**,
style: const TextStyle(fontSize: 16),
),
);
},
selectedItemBuilder: (T t, VoidCallback deleteSelectedItem) {
//TODO: navigate here to user profile
return null;
},
// widget customization
noItemsFoundWidget: Center(
child: Text("No item Found"),
),
textFieldBuilder: (TextEditingController controller, FocusNode focusNode) {
return SearchTextField(controller, focusNode);
},
);
}
and I can use the search method like that.
class AllEmployeeScreen extends StatelessWidget {
List<Employee> employee;
#override
Widget build(BuildContext context) {
return search(employee, context);
}
}
assuming that I have data from Firestore inside my list.
the problem is when I use myList.name it says "The getter 'name' isn't defined for the class 'List<'T'>'. "
shouldn't it give that error only when I pass List<'Employee'> to search method without 'name' parameter into it?!
This error means you are trying to access the name on a List of data, which is not there. check this code in search method:
return myList
.where(
(item) => myList.name.toLowerCase().contains(query.toLowerCase()))
.toList();
As you can see, myList.name is giving you this error.
Since where is giving you a reference name for an object in the list as item the code should be:
return myList
.where(
(item) => item.name.toLowerCase().contains(query.toLowerCase()))
.toList();
This should resolve your error.
On an additional note:
Type generic methods work best when you provide the type of data you are going to perform a set of operations on. But that also means that the code you are writing in that method should work for all the possible Data types denoted by T.
In this case search() is specific to the User type of object. In this case, you should not use type generics at all.
A good example of type Generics would be:
bool isEmpty<T extends Iterable>(T dataCollection){
return dataCollection == null || dataCollection.isEmpty;
}
As you can see, in this example we use generics to allow all types of collections which are subclasses of Iterable class.
While this allows us support to a range of data types, it also makes sure that the passed data type is supported or not on compile time.

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?