I've made a chat app and I want the user to be able to mark chatroom as favourite or mute the chatroom.
The structure of my database is like the image below
What I want is when user is trying to mute a chatroom or mark it as favourite,
I only update the item that matches the chatroomId, But after a lot of searching, I found out Firebase won't allow you to update an element of an array and you can only add or remove from an array.
This is my function to mute or mark a chatroom as favourite:
static final Firestore dbReference = Firestore.instance;
static Future<void> currentUserChatroomChangeMuteOrFavouriteMethod({
#required String chatroomId,
#required bool muteValue,
#required bool favouriteValue,
#required ChatroomMuteFavouriteOption operationToBeDone,
}) async {
DocumentReference docRef = dbReference
.collection("user_chatrooms")
.document(CurrentUserDetails.id);
DocumentSnapshot docSnapshot = await docRef.get();
Map<String, dynamic> docData = docSnapshot.data;
List<Map<String, dynamic>> userChatrooms =
(docData["chatrooms"] as List<dynamic>)
.map((chatroom) => Map<String, dynamic>.from(chatroom))
.toList();
for (int index = 0; index < userChatrooms.length; index++) {
Map<String, dynamic> chatroom = userChatrooms[index];
if (chatroom["chatroomId"] == chatroomId) {
if (operationToBeDone == ChatroomMuteFavouriteOption.muteOperation) {
chatroom["isMuted"] = !muteValue;
} else {
chatroom["isMarkedFavourite"] = !favouriteValue;
}
break;
}
}
await docRef.updateData({"chatrooms": userChatrooms});
}
So basically I get all the data, I update the field I want and I updateData again but this is not a good solution as I am also getting the chatrooms which I don't need.
How can I do this operation effeciently?
Thanks.
It's a perfectly fine solution. In order to update the contents of an array, you do have to read the document, modify the array in memory, then update the document with the new array. There's really no way to make this more efficient, other than converting your array to documents in a subcollection.
Related
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) {
...
}
I am new to flutter and am writing a new app. In the app I want to retrieve the user data from a firebase document and populate TextFields on the user screen. I can pull the data fine using DocumentSnapShot. I want to use TextEditingController and populate it with the data but I am not able to do it. I know the data is there since I can see it in debug mode. Below are snippets of my code.
Here is where I get the data and populate the TextEditingControllers:
final agentsRef = FirebaseFirestore.instance.collection(('agents'));
getCurrentAgent() async {
final DocumentSnapshot currentAgent =
await agentsRef.doc(globals.currentUid).get();
if (currentAgent == null) {
emailController.text = "";
new Future.delayed(Duration.zero, () {
final agentProvider =
Provider.of<AgentProvider>(context, listen: false);
agentProvider.loadValues(Agents());
});
} else {
// existing record
// Updates Controllers
**emailController.text = currentAgent.data[index].toString(); // THIS LINE IS WHERE I AM HAVING TROUBLE**
// Updates State
new Future.delayed(Duration.zero, () {
final agentProvider =
Provider.of<AgentProvider>(context, listen: false);
agentProvider.loadValues(widget.agents);
});
}
#override
void initState() {
getCurrentAgent();
super.initState();
}
How do I access each data element in the DocumentSnapShot?
You'll typically get the fields by their name and not by their index. So:
**emailController.text = currentAgent.data()["email"].toString();
void initState() {
// TODO: implement initState
super.initState();
getusers().then((dataf){
snapshots=dataf.documents[1];// In here
});
}
final db=Firestore.instance;
DocumentSnapshot snapshots;
Future<QuerySnapshot> getusers(){
return db.collection("Users").document(widget.userid).collection("UsersJoined").getDocuments();
}
I want to get all the documents and fields inside documents without referring to their index. Is it possible by a loop or something to loop through the whole documents and get all their fields
To loop over the documents in the QuerySnapshot:
getusers().then((dataf){
for (var userDoc in dataf.documents) {
print(userDoc["name"] // get the value of one field
var data = userDoc.data // get all data as a Map<String, Object>
})
});
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.
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.