Flutterfire deleting item from database - firebase

I am trying to delete a specific item from a database given only its element values. However, I keep getting the error message "The type 'Stream<QuerySnapshot<Object?>>' used in the 'for' loop must implement Iterable" over the items.snapshot() section. What am I doing wrong there, because I thought that it would get me all the snapshots of the documents? The deleteName, Type, and Location are all String variables that I defined earlier
CollectionReference items = FirebaseFirestore.instance.collection('items');
Object deleteUser() {
// Call the user's CollectionReference to add a new user
if (name != "" && type != "" && location != "") {
for (var doc in items.snapshots()) {
if (doc.data['name'] == deleteName &&
doc.data['type'] == deleteType &&
doc.data['location'] == deleteLocation) {
doc.delete();
}
}
return items;
} else {
return "There was a null error";
}
}

Your code is a little confusing, if your getting items from Firestore, you will want to map it to an object for iterating through.
Item(
String id; //Give an id value for document ID in firestore
String name;
String type;
String location;
);
//Get Items
CollectionReference itemsCollection = FirebaseFirestore.instance.collection('items');
List<Item> items = itemsCollection.snapshots().map(itemsSnapshot)
//Map Items
List<Item> itemsSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((DocumentSnapshot doc) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
return Item(
id: doc.reference.id, //this is your reference to firestore
name: data['name'],
type: data['type'],
location: data['location'],
);
}).toList();
}
//Iterate through list of items
for(Item item in items){
//do any checks you want here
if(item.name == "Nobody"){
//delete document
itemsCollection.doc(item.id).delete();
}
}

Your items is a CollectionReference object, and calling snapshots() returns a Stream, which is you can't loop over with a for loop.
My guess is that you're looking to use get instead of snapshots, so that you can then await the result and process it:
CollectionReference itemsCol = FirebaseFirestore.instance.collection('items');
var itemsSnapshot = await itemsCol.get();
for (var doc in itemsSnapshot.docs) {
...
}

Related

How to pull snapshot key & values into list through Map<>?

I've been following the official Firebase tutorial for using a real-time database: https://www.youtube.com/watch?v=sXBJZD0fBa4
I am able to pull all the data from the firebase real-time database. However, the method below to do so, provides a list of the data, with no reference to the parent keys (snapshot.key). An ideal scenario would be to have a key property within the Item class (item.key), so I can call upon it directly from the list.
class DatabaseModel {
final itemsRef = FirebaseDatabase.instance.ref().child('/Contents');
Stream<List<Items>> getItemssStream() {
final itemsStream = itemsRef.onValue;
final streamToPublish = itemsStream.map((event) {
final itemsMap = Map<String, dynamic>.from(event.snapshot.value as Map<String, dynamic>);
final itemsList = itemsMap.entries.map((element) {
return Items.fromRTDB(Map<String, dynamic>.from(element.value));
}).toList();
return itemsList;
});
return streamToPublish;
}
}
class Items{
final String item;
final String expiryDate;
final String quantity;
final String user;
Items({required this.item, required this.expiryDate, required this.quantity, required this.user});
//Mapping from real-time database
factory Items.fromRTDB(Map<String, dynamic> data) {
return Items(
item: data['item'],
expiryDate: data['exp'],
quantity: data['qty'],
user: data['user'],
);
}
}
In this code you only use the element.value of each node in your results:
return Items.fromRTDB(Map<String, dynamic>.from(element.value));
If you also want to get the key of each item, you will have to also use element.key in there and pass that to your Items object.
Something like this:
Items.fromRTDB(element.key, Map<String, dynamic>.from(element.value));
...
class Items{
final String key;
final String item;
final String expiryDate;
final String quantity;
final String user;
Items({required this.key, required this.item, required this.expiryDate, required this.quantity, required this.user});
//Mapping from real-time database
factory Items.fromRTDB(String key, Map<String, dynamic> data) {
return Items(
key: key,
item: data['item'],
expiryDate: data['exp'],
quantity: data['qty'],
user: data['user'],
);
}
}

Data is null when trying to add auto generated document ID from firebase to object. Why?

I'm streaming a collection from firebase and I want to add the auto generated ID into ProductModel object.
Stream<List<ProductModel>> getprod() {
return _firestore
.collection('macarons')
.snapshots()
.map((QuerySnapshot query) {
List<ProductModel> retVal = [];
query.docs.forEach((element) {
retVal.add(ProductModel.fromJson(element.data(), element.id));
print(element.id);
});
return retVal;
});
}
This is my model
ProductModel.fromJson(
Map<String, dynamic> prod,
String id,
) {
id = id;
description = prod['description'] ?? 'Classic macaron';
flavor = prod['flavor'] ?? 'Choclate';
name = prod["name"] ?? 'empty';
price = prod["price"] ?? 5;
imageurl = prod["imageurl"] ?? 'www.google.com';
}
I do get the ID printed out however it is not rendering on my widget. I get the 'data != null' assertion.
Your problem is in:
id = id;
The left-hand side of the expression is also interpreted as the id parameter, so the assignment becomes a noop.
You'll want to do this instead:
this.id = id;

"native "AssertionError_throwNew" " error with Firebase Firestore

I have this function that checks if a document exists or not, if exists returns true otherwise false :
Future<bool> checkMissingId(String id) async {
String str = id.toLowerCase();
String letter = str[0];
String path = letter + "/" + str;
try {
final snapShot =
await FirebaseFirestore.instance.collection(path).doc(str).get();
if (snapShot == null || !snapShot.exists) {
return true;
} else
return false;
} catch (e) {
print(e);
return false;
}
}
But when I call it from here (after save and validate form ) :
Future<void> _submit() async {
//Create Artist and send it to the database
if (_validateAndSaveForm()) {
await checkMissingId(userNameF);
}
}
All freezes and it opens a new file called "errors_patch.dart" with this exception :
static _doThrowNew(int assertionStart, int assertionEnd, Object? message)
native "AssertionError_throwNew";
I think the problem is that checkMissingId is of type Future and maybe I'm not handling futures in the right way...But the error highlights also await FirebaseFirestore.instance.collection(path).doc(str).get(); so I don't know exactly how isolate the problem.
This is the stack :
assert(isValidCollectionPath(collectionPath),
await FirebaseFirestore.instance.collection(path).doc(str).get();
with a red quotes :
_AssertionError ('package:cloud_firestore/src/firestore.dart': Failed assertion: line 74 pos 12: 'isValidCollectionPath(collectionPath)': a collection path must point to a valid collection.)
await checkMissingId(userNameF);
Maybe it depends by wrong document path from Firestore?
I think you are passing the wrong collection name in this line:
final snapShot = await FirebaseFirestore.instance.collection(path).doc(str).get();
because, your path variable is this:
String path = letter + "/" + str; // I guess here is something wrong
Your collection name would be a fixed string as shown below in this screenshot (comments, posts, users etc):

How do I write tests for a transformed stream in Flutter?

I have a Provider which has a method which takes data from Firebase as a stream, transforms it to a list and returns a Stream<List<Model>> . I'm trying to write a test where I want to check if the items in the List are the same as I expect them to be. How can I do that?
My Current Code:
test('getContacts returns a empty list when there is no contact',() async{
when(sharedPreferencesMock.get(any)).thenReturn('uid'); //mock the sharedprefs
documentSnapshot = DocumentSnapshotMock(); //mock documentsnapshot
when(documentSnapshot.exists).thenReturn(true); // this is done to pass the getUidByUsername method
documentReference = DocumentReferenceMock(documentSnapshotMock: documentSnapshot);
documentReference.setData({
'uid':'uid',
'contacts':[] // setting the usename in the data already so that duplicate contact exception is thrown
});
userDataProvider.getContacts().asBroadcastStream().listen((data){
expect(data.length,0);
});
});
And the provider method
#override
Stream<List<Contact>> getContacts() {
CollectionReference userRef = fireStoreDb.collection(Paths.usersPath);
DocumentReference ref =
userRef.document(SharedObjects.prefs.get(Constants.sessionUid));
return ref.snapshots().transform(StreamTransformer<DocumentSnapshot, List<Contact>>.fromHandlers(handleData: (documentSnapshot, sink) async{
List<String> contacts;
if (documentSnapshot.data['contacts'] == null) {
ref.updateData({'contacts': []});
contacts = List();
} else {
contacts = List.from(documentSnapshot.data['contacts']);
}
List<Contact> contactList = List();
for (String username in contacts) {
print(username);
String uid = await getUidByUsername(username);
DocumentSnapshot contactSnapshot = await userRef.document(uid).get();
contactList.add(Contact.fromFirestore(contactSnapshot));
}
sink.add(contactList);
}));
}
Update:
StreamController streamController = StreamController<List<Contact>>();
StreamSink<List<Contact>> sink = streamController.sink;
Stream<List<Contact>> stream = streamController.stream;
stream.listen((List<Contact> list){
expect(list.length,1);
});
userDataProvider.mapDocumentToContact(userCollection, userRef, documentSnapshot, sink);
streamController.close();
Make the lambda function that you currently pass to the StreamTansformer a separate function and test that.
If you want to test the full function there is a Firebase mock package on pub.

How to convert a sub collection to list with flutter and firestore?

I have an object Dish who containes a list of ingredients and I want to get them. How can I do?
In Firebase, Dish is a Document and Ingredient is a sub collection. I tried this but it doesn't work.
class Dish{
String name;
DocumentReference reference;
List<Ingredient> ingredients;
Dish.fromMap(Map<String, dynamic> map, {this.reference}){
this.name = map['name'];
this
.reference
.collection("ingredients")
.snapshots()
.listen((QuerySnapshot snap) {
final List<DocumentSnapshot> ingredientsDocuments = snap.documents;
List<Ingredient> ing = [];
for (var i = 0; i < ingredientsDocuments.length; i++) {
ing.add(Ingredient.fromSnapshot(ingredientsDocuments[i]));
}
this.ingredients = ing;
});
}
Dish.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Dish<$String>";
}
class Ingredient{
final String name;
final DocumentReference reference;
Ingredient.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
name = map['name'];
Ingredient.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Ingredient<$String>";
}
How are you trying to fetch data from Firestore using Dish class? Were you using any asynchronous task(i.e. Future)? What's not working in your implementation? Any errors that you received?
Since I'm unable to run the repro you've provided, here's a sample code that you can try.
List<Ingredient> ingredients;
// call getDocuments() to fetch data from Firestore and add it to the List
Future<void> getDocuments() async {
ingredients = List();
var collection = FirebaseFirestore.instance
.collection('ingredients');
collection.get().then((value) {
value.docs.forEach((element) {
setState(() {
// add the object to the List
ingredients.add(Ingredient(Ingredient.fromMap(element.data())));
});
});
});
}
As for the Object, it can be as simple as this. No need to pass DocumentReference since we'll only be using it to map the data to the Object and be able to add it in the List.
class Ingredients {
var name;
Ingredients(Ingredients document) {
this.documentName = document.getName();
}
dynamic getName() => name;
Ingredients.fromMap(Map<dynamic, dynamic> document)
: name = document['name'];
}
You can check a working sample I've posted in here. It has pagination added, but it should have a similar approach with the code snippets I've shared here.

Resources