New document being created instead of updated - firebase

I have an onPress on an edit page - it should update the document when edited and saved.
However, it is currently creating a new document with that data instead.
onPressed: () async {
//controllers...
await updateContact(context);
Navigator.pop(context, widget.contact);
}
.
final db = FirebaseFirestore.instance;
.
Future updateContact(context) async {
final uid = await TheProvider.of(context).auth.getCurrentUID();
await db
.collection('userData')
.doc(uid)
.collection('Contacts')
.doc(widget.contact.documentId)
.set({
'Name': oneController.text,
'PhoneNumber': int.tryParse(twoController.text),
'Location': threeController.text,
'Rating': int.tryParse(fourController.text),
'Instagram': fiveController.text,
'Birthday': int.tryParse(sixController.text),
'Notes': sevenController.text},
SetOptions(merge: true));
.
Contact.fromSnapshot(DocumentSnapshot snapshot) :
//...
documentId = snapshot.id;
I am not sure how to best resolve this.

Yes, using set() will override all the data already present in firestore.
and yes, using update is the way to go, but keep in mind not to call .toJson() on the entire object as update only takes the fields that are needed to be updated.
So if you update with the entire object, it'll create a new one again.
You could pass it like this
.update({'name': oneController.text, 'birth': twoContorller.text, 'email': threeController.text});
alternatively, you can also use set( setOptions: SetOptions(merge:true)) this will update only the fields that have changed in the document.

Related

Flutter Firestore Update Where

I'm trying to run a query that retrieves a single row given a where clause and updates it. I understand that Firebase doesn't support an UpdateWhere operations so I'm trying to use a Transaction instead.
I'm having difficulty making it work, maybe I'm too used to sql dbs... Here's my broken code
try {
final whereQuery = _db
.doc(userPath(user))
.collection("someInnerCollection")
.where("active", isEqualTo: true)
.limit(1);
await _db.runTransaction((transaction) async {
final entry = await transaction.get(whereQuery); // This doesn't compile as .get doesn't take in a query
await transaction.update(entry, {
"someValue": "newValue",
});
});
} catch (e) {
...
}
From the test I’ve made, I would suggest the following to achieve what you mention:
Based on the following answer:
As you can see from the API documentation, where() returns a Query object. It's not a DocumentReference.
Even if you think that a query will only return one document, you still have to write code to deal with the fact that it could return zero or more documents in a QuerySnapshot object. I suggest reviewing the documentation on queries to see examples.
After doing the query consult, you have to get the DocumentReference for that given result.
Then, you can use that reference to update the field inside a Batched writes
try {
final post = await firestore
.collection('someInnerCollection')
.where('active', isEqualTo: true)
.limit(1)
.get()
.then((QuerySnapshot snapshot) {
//Here we get the document reference and return to the post variable.
return snapshot.docs[0].reference;
});
var batch = firestore.batch();
//Updates the field value, using post as document reference
batch.update(post, { 'someValue': 'newValue' });
batch.commit();
} catch (e) {
print(e);
}
You are passing the DocumentSnapshot back in the update() operation instead of DocumentReference itself. Try refactoring the like this:
final docRefToUpdate = _db.collection("colName").doc("docId");
await _db.runTransaction((transaction) async {
final entry = await transaction.get() // <-- DocRef of document to update in get() here
await transaction.update(docRefToUpdate, {
// Pass the DocumentReference here ^^
"someValue": "newValue",
});
});
You can use a collection reference and then update single fields using .update().
final CollectionReference collectionReference = FirebaseFirestore.instance.collection('users');
await collectionReference.doc(user.uid).collection('yourNewCollection').doc('yourDocumentInsideNestedCollection').update({
'singleField': 'whatever you want,
});
Same code using "where"
collectionReference.doc(user.uid).collection('yourNewCollection').doc().where('singleField', isEqualTo: yourValue).update({
'singleField': 'whatever you want,
});

How can I check if a document id that already exists in Firestore using Flutter [duplicate]

I want to add data into the firestore database if the document ID doesn't already exists.
What I've tried so far:
// varuId == the ID that is set to the document when created
var firestore = Firestore.instance;
if (firestore.collection("posts").document().documentID == varuId) {
return AlertDialog(
content: Text("Object already exist"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () {}
)
],
);
} else {
Navigator.of(context).pop();
//Adds data to the function creating the document
crudObj.addData({
'Vara': this.vara,
'Utgångsdatum': this.bastFore,
}, this.varuId).catchError((e) {
print(e);
});
}
The goal is to check all the documents ID in the database and see in any matches with the "varuId" variable. If it matches, the document won't be created. If it doesn't match, It should create a new document
You can use the get() method to get the Snapshot of the document and use the exists property on the snapshot to check whether the document exists or not.
An example:
final snapShot = await FirebaseFirestore.instance
.collection('posts')
.doc(docId) // varuId in your case
.get();
if (snapShot == null || !snapShot.exists) {
// Document with id == varuId doesn't exist.
// You can add data to Firebase Firestore here
}
Use the exists method on the snapshot:
final snapShot = await FirebaseFirestore.instance.collection('posts').doc(varuId).get();
if (snapShot.exists){
// Document already exists
}
else{
// Document doesn't exist
}
To check if document exists in Firestore. Trick is to use .exists method
FirebaseFirestore.instance.doc('collection/$docId').get().then((onValue){
onValue.exists ? // exists : // does not exist ;
});
I know this is a flutter firestore topic but I just want to share my answer.
I am using Vue and I am also doing a validation if the id is already taken on firestore.
This is my solution as of firebase version 9.8.2
const load = async() => {
try {
const listRef = doc(db, 'list', watchLink.value);
let listSnapShot = await getDoc(listRef);
if(listSnapShot._document == null) {
await setDoc(doc(db, 'list', watchLink.value), {
listName: NameofTheList.value
});
throw Error('New list added');
}
else {
throw Error('List already Exist');
}
} catch (error) {
console.log(error.message);
}
}
load();
The watchLink.value is the ID that you want to check
Edit:
if you console.log(listSnapShot), the _document will be set to null if the id does not exist on firestore. See screenshot below
If it does not exist
If ID already exists
QuerySnapshot qs = await Firestore.instance.collection('posts').getDocuments();
qs.documents.forEach((DocumentSnapshot snap) {
snap.documentID == varuId;
});
getDocuments() fetches the documents for this query, you need to use that instead of document() which returns a DocumentReference with the provided path.
Querying firestore is async. You need to await its result, otherwise you will get Future, in this example Future<QuerySnapshot>. Later on, I'm getting DocumentSnapshots from List<DocumentSnapshots> (qs.documents), and for each snapshot, I check their documentID with the varuId.
So the steps are, querying the firestore, await its result, loop over the results. Maybe you can call setState() on a variable like isIdMatched, and then use that in your if-else statement.
Edit: #Doug Stevenson is right, this method is costly, slow and probably eat up the battery because we're fetching all the documents to check documentId. Maybe you can try this:
DocumentReference qs =
Firestore.instance.collection('posts').document(varuId);
DocumentSnapshot snap = await qs.get();
print(snap.data == null ? 'notexists' : 'we have this doc')
The reason I'm doing null check on the data is, even if you put random strings inside document() method, it returns a document reference with that id.

How to update collection documents in firebase in flutter?

I want to update a document field and I've tried the following code but it doesn't update.
can anyone give me a solution, please?
My Code:
var snapshots = _firestore
.collection('profile')
.document(currentUserID)
.collection('posts')
.snapshots();
await snapshots.forEach((snapshot) async {
List<DocumentSnapshot> documents = snapshot.documents;
for (var document in documents) {
await document.data.update(
'writer',
(name) {
name = this.name;
return name;
},
);
print(document.data['writer']);
//it prints the updated data here but when i look to firebase database
//nothing updates !
}
});
For cases like this, I always recommend following the exact types in the documentation, to see what options are available. For example, a DocumentSnapshot object's data property is a Map<String, dynamic>. To when you call update() on that, you're just updating an in-memory representation of the document, and not actually updating the data in the database.
To update the document in the database, you need to call the DocumentReference.updateData method. And to get from the DocumentSnapshot to a DocumentReference, you call the DocumentSnapshot.reference property.
So something like:
document.reference.updateData(<String, dynamic>{
name: this.name
});
Unrelated to this, your code looks a bit non-idiomatic. I'd recommend using getDocuments instead of snapshots(), as the latter will likely result in an endless loop.
var snapshots = _firestore
.collection('profile')
.document(currentUserID)
.collection('posts')
.getDocuments();
await snapshots.forEach((document) async {
document.reference.updateData(<String, dynamic>{
name: this.name
});
})
The difference here is that getDocuments() reads the data once, and returns it, while snapshots() will start observing the documents, and pass them to us whenever there's a change (including when you update the name).
Update 2021:
Lot of things have changed in the API, for example, Firestore is replaced by FirebaseFirestore, doc is in, etc.
Update a document
var collection = FirebaseFirestore.instance.collection('collection');
collection
.doc('some_id') // <-- Doc ID where data should be updated.
.update({'key' : 'value'}) // <-- Updated data
.then((_) => print('Updated'))
.catchError((error) => print('Update failed: $error'));
Update nested value in a document:
var collection = FirebaseFirestore.instance.collection('collection');
collection
.doc('some_id') // <-- Doc ID where data should be updated.
.update({'key.foo.bar' : 'nested_value'}) // <-- Nested value
.then((_) => print('Updated'))
.catchError((error) => print('Update failed: $error'));
To update some fields of a document without overwriting the entire document, use the following language-specific update() methods:
final washingtonRef = FirebaseFirestore.instance.collection("cites").doc("DC");
washingtonRef.update({"capital": true}).then(
(value) => print("DocumentSnapshot successfully updated!"),
onError: (e) => print("Error updating document $e"));
Server Timestamp
You can set a field in your document to a server timestamp which tracks when the server receives the update.
final docRef = FirebaseFirestore.instance.collection("objects").doc("some-id");
final updates = <String, dynamic>{
"timestamp": FieldValue.serverTimestamp(),
};
docRef.update(updates).then(
(value) => print("DocumentSnapshot successfully updated!"),
onError: (e) => print("Error updating document $e"));
Update fields in nested objects
If your document contains nested objects, you can use "dot notation" to reference nested fields within the document when you call update():
// Assume the document contains:
// {
// name: "Frank",
// favorites: { food: "Pizza", color: "Blue", subject: "recess" }
// age: 12
// }
FirebaseFirestore.instance
.collection("users")
.doc("frank")
.update({"age": 13, "favorites.color": "Red"});
Update elements in an array
If your document contains an array field, you can use arrayUnion() and arrayRemove() to add and remove elements. arrayUnion() adds elements to an array but only elements not already present. arrayRemove() removes all instances of each given element.
final washingtonRef = FirebaseFirestore.instance.collection("cities").doc("DC");
// Atomically add a new region to the "regions" array field.
washingtonRef.update({
"regions": FieldValue.arrayUnion(["greater_virginia"]),
});
// Atomically remove a region from the "regions" array field.
washingtonRef.update({
"regions": FieldValue.arrayRemove(["east_coast"]),
});
Increment a numeric value
You can increment or decrement a numeric field value as shown in the following example. An increment operation increases or decreases the current value of a field by the given amount.
var washingtonRef = FirebaseFirestore.instance.collection('cities').doc('DC');
// Atomically increment the population of the city by 50.
washingtonRef.update(
{"population": FieldValue.increment(50)},
);

Flutter firestore - Check if document ID already exists

I want to add data into the firestore database if the document ID doesn't already exists.
What I've tried so far:
// varuId == the ID that is set to the document when created
var firestore = Firestore.instance;
if (firestore.collection("posts").document().documentID == varuId) {
return AlertDialog(
content: Text("Object already exist"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () {}
)
],
);
} else {
Navigator.of(context).pop();
//Adds data to the function creating the document
crudObj.addData({
'Vara': this.vara,
'Utgångsdatum': this.bastFore,
}, this.varuId).catchError((e) {
print(e);
});
}
The goal is to check all the documents ID in the database and see in any matches with the "varuId" variable. If it matches, the document won't be created. If it doesn't match, It should create a new document
You can use the get() method to get the Snapshot of the document and use the exists property on the snapshot to check whether the document exists or not.
An example:
final snapShot = await FirebaseFirestore.instance
.collection('posts')
.doc(docId) // varuId in your case
.get();
if (snapShot == null || !snapShot.exists) {
// Document with id == varuId doesn't exist.
// You can add data to Firebase Firestore here
}
Use the exists method on the snapshot:
final snapShot = await FirebaseFirestore.instance.collection('posts').doc(varuId).get();
if (snapShot.exists){
// Document already exists
}
else{
// Document doesn't exist
}
To check if document exists in Firestore. Trick is to use .exists method
FirebaseFirestore.instance.doc('collection/$docId').get().then((onValue){
onValue.exists ? // exists : // does not exist ;
});
I know this is a flutter firestore topic but I just want to share my answer.
I am using Vue and I am also doing a validation if the id is already taken on firestore.
This is my solution as of firebase version 9.8.2
const load = async() => {
try {
const listRef = doc(db, 'list', watchLink.value);
let listSnapShot = await getDoc(listRef);
if(listSnapShot._document == null) {
await setDoc(doc(db, 'list', watchLink.value), {
listName: NameofTheList.value
});
throw Error('New list added');
}
else {
throw Error('List already Exist');
}
} catch (error) {
console.log(error.message);
}
}
load();
The watchLink.value is the ID that you want to check
Edit:
if you console.log(listSnapShot), the _document will be set to null if the id does not exist on firestore. See screenshot below
If it does not exist
If ID already exists
QuerySnapshot qs = await Firestore.instance.collection('posts').getDocuments();
qs.documents.forEach((DocumentSnapshot snap) {
snap.documentID == varuId;
});
getDocuments() fetches the documents for this query, you need to use that instead of document() which returns a DocumentReference with the provided path.
Querying firestore is async. You need to await its result, otherwise you will get Future, in this example Future<QuerySnapshot>. Later on, I'm getting DocumentSnapshots from List<DocumentSnapshots> (qs.documents), and for each snapshot, I check their documentID with the varuId.
So the steps are, querying the firestore, await its result, loop over the results. Maybe you can call setState() on a variable like isIdMatched, and then use that in your if-else statement.
Edit: #Doug Stevenson is right, this method is costly, slow and probably eat up the battery because we're fetching all the documents to check documentId. Maybe you can try this:
DocumentReference qs =
Firestore.instance.collection('posts').document(varuId);
DocumentSnapshot snap = await qs.get();
print(snap.data == null ? 'notexists' : 'we have this doc')
The reason I'm doing null check on the data is, even if you put random strings inside document() method, it returns a document reference with that id.

FLUTTER | How to add data to an existing document in firestore

I'm using firestore to store data of my flutter application, and I made a function that creates a document in firestore automatically after the user login
Now I want the user when he fills this form , the data will be added in the same document where the user's email exists.
RaisedButton(
child: Text("Submit"),
onPressed: () {
final CollectionReference users = Firestore.instance.collection('users');
Firestore.instance
.runTransaction((Transaction transaction) async {
CollectionReference reference =
Firestore.instance.collection('users');
await reference
.add({"fullname": nameController.text, "PhoneNumber": phoneController.text, "adresse": adressController.text});
nameController.clear();
phoneController.clear();
adressController.clear();
});}
I tried this code but it adds new document.
Specify document name before updating database.
Firestore.instance
.collection('Products')
.document('Apple')
.updateData({
'price': 120,
'quantity': 15
});
Here my price and quantity data are numbers. If yours are Strings put String values there.
Best practice is to use transaction.
Make sure that document reference is a reference to a file that you wish to update.
Firestore.instance.runTransaction((transaction) async {
await transaction.update(
documentReference, data);
};
It will make sure that update happens in order in case there are many clients doing it.
In the case of a concurrent edit, Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data.
More info here
Try .setData({"fullname": nameController.text, "PhoneNumber": phoneController.text, "adresse": adressController.text}, merge: true).
Update 2021:
You need to update the data to add it to an existing document.
var collection = FirebaseFirestore.instance.collection('users');
collection
.doc('doc_id') // <-- Doc ID where data should be updated.
.update({'age' : 20}) // <-- New data
.then((_) => print('Updated'))
.catchError((error) => print('Update failed: $error'));

Resources