Firestore document update is failing - firebase

i have documents in firestore which i am trying to update. These updates are successful for old users but failing for new users. I have listview where user can see all the added documents. They click on Edit and then they are navigated to a detailed document view where they can make changes and save.
Error which i am receiving
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception:
[cloud_firestore/not-found] Some requested document was not found.
I am passing document id with following from list view to single document edit view;
data: docId
In the edit page, i am loading the user id and doc id which is coming from previous page.
class Edit extends StatefulWidget {
final DataModel data;
Edit({required this.data});
String ui = FirebaseAuth.instance.currentUser!.uid;
#override
_EditState createState() => _EditState();
}
On save button i am updating firestore with following;
ElevatedButton.styleFrom(primary: Colors.redAccent),
onPressed: () async {
DocumentReference edit = FirebaseFirestore
.instance
.collection('edit')
.doc(ui)
.collection('edit')
.doc(widget.data.documentId);
edit.update({
});

The cloud_firestore/not-found error means the document was not found under the specified id. Please make sure the id really exists in your database.
If you are using the local emulator, make sure you are accessing the correct database.
If you are using persistent storage (offline firestore data) it might be that it still has old data or your device didn't connect to the internet and thus doesn't have the current data.

The error message is telling you that the document you build a reference to was not found for update.
There are a few ways to update data to Cloud Firestore:
update - change fields on a document that already exists.
Fails if the document does not exist.
set - overwrite the data in a document, creating the
document if it does not already exist. If you want to only partially
update a document, use SetOptions.merge(). If you want to be able to
create a document if it doesn't exist, or update it if it does
already exist, you should pass SetOptions.merge as the second
parameter, as explained by the API documentation.
So you just need to use the correct operation for your use case.
If you want to update a document without knowing if it exists or not,
you want to use a set() with the merge() option like this (this
example is for Java):
// Update one field, creating the document if it does not already exist.
Map<String, Object> data = new HashMap<>();
data.put("capital", true);
db.collection("cities").document("BJ") .set(data, SetOptions.merge());
Also one classical problem when using auth.currentUser is that it is
possible that the Auth object is not fully initialized and that
auth.currentUser.uid is therefore null. As explained in the doc you
should either use the onAuthStateChanged() observer or check that
auth.currentUser is not null.
Try :
ElevatedButton.styleFrom(primary: Colors.redAccent),
onPressed: ()
async {
DocumentReference edit = FirebaseFirestore .instance .collection('edit') .doc(FirebaseAuth.instance.currentUser.uid) .collection('edit') .doc(widget.data.documentId);
edit.update({ });

Related

How to update a list automatically using Firestore Stream?

In my app, I am now using a "refresh function" to update a list in Provider. When the user swipe, I call Refreshlist in my provider and with NotifyListeners() it updates my UI just fine. (The UI is linked to the list _myEleves).
I am afraid that users might use this "refresh" button too many times making unnecessary calls and "reads" on firebase and so increasing artificially the number of reads so the costs of Firebase.
Here is the code :
Future<void> refreshEleveList() async {
final DocumentSnapshot<Map<String, dynamic>> docInfo =
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail.toLowerCase())
.get();
_mesEleves = (docInfo['mesEleves'] as List<dynamic>)
.map((data) => Eleve.fromMap(data))
.toList();
notifyListeners();
}
I have been reading about STREAMS a lot, but I just can't get it right on how to start this stream, the listening to the changes on Firebase inside my PROVIDER file, so that changes will be made to "_myEleves" list.
What I want to do is that each time a change on firebase happens, it updates my list "_myEleves". Is there a simple way to do this ?
My Provider covers the whole app (I use it in the MAIN file). I thought of adding a StreamProvider, but the thing is I don't want this stream to start until user is authentified etc... and userInfo is first downloaded.
Right now : when user logs in : it downloads from firebase all necessary info, _mesEleves being one of them (This is for a teacher). Whenever a new student joins the group, it modifies firebase and so it should stream down this info into "myEleves" list on the teacher account.
Anybody can help ?

Flutter Firestore not giving updated data when I add a field to a document from the console

I'm facing an odd bug in Flutter Firestore 2.5.3 where if I add a new field (customer_type) to a document in the Firebase Console, the app fails to retrieve the newly added field and throws an exception:
CastError (type 'Null' is not a subtype of type 'String' in type cast)
But it isn't null. I added the new field with the value from the Firebase Console. It's there in the document. Turns out Firebase is getting the old document data without the newly added field.
I'm using a StreamProvider from Riverpod. Here's the code:
static Stream<Customer?> _watchCustomer(final ProviderRefBase ref) async* {
final auth = await ref.watch(authProvider.last);
if (auth == null) {
yield null;
} else {
final customerRef = _db.collection('customers').toCustomerRef(auth.uid);
await for (var customer in customerRef.snapshots().map((doc) => doc.data())) yield customer;
}
}
What could be going wrong?
EDIT:
All the names, collection & field are correct and so is their type. If I re-install the app or fully clear app storage, the same exact code will work just fine. However, if I just clear the cache it doesn't work. It impedes my workflow when debugging and I'd like to know why it's happening. For additional reference, here is my "Customer" model:
Customer.fromMap(final String documentID, final Map<String, dynamic> map)
: id = documentID,
name = map['customer_name'] as String,
mobile = map['customer_mobile'] as String,
type = map['customer_type'] as String;
It's set to be non-nullable, because I'm establishing a strict schema. But that shouldn't be a concern, cause it isn't null in the database. I've added customer_type to all my documents in the collection. And none of them have the value set to null.
Which could only mean that the map that I get from Firestore, couldn't find the requisite key. Which means I'm still getting the old data (without the new field) from Firestore. What gives? It should give me the updated data when I clear the cache at least.
Because your code is working properly until you add the new field in Firestore console, it means that the problem is with your new field.
The problem is you either added a field in Firestore spelled differently (case sensitive or typo) from your model (class Customer) or the type is not appropriate (e.g. you try to read an int from a String, or you try to read an array from a map, etc.)
Let me know if this does not help?
What I know so far is that the stream returns an immediate local snapshot of the Document before fetching the updated version from the server. Which makes sense; except that if I clear the cache, then it should go to the server directly and not give me any error - which doesn't happen.
I think Firestore might be storing the local snapshot in more places than just the cache, but this is merely speculation on my end.
Currently, I've patched the issue by doing a simple null check in the constructor:
type = map['customer_type'] as String? ?? 'customer'
This fix is, however, inconclusive and I won't be marking this as the answer as I feel a proper explanation of what's happening is due.

Firestore realtime listener detect the type of change for a single doc only

I saw on the Firestore documentation for realtime listener that we can view changes between snapshots and see whether each document is added, removed or modified.
I am wondering if it is possible to see the type of changes if I am only attaching onSnapshot to a single document?
I tried to run the docChanges() method on the single doc listener:
db.collection("matching").doc("user1").onSnapshot(async doc => {
doc.docChanges().forEach(function(change) {
if (change.type === "added") {
console.log("added: " + change.doc.data());
}
})
})
But it produced an error of :
Uncaught (in promise) TypeError: doc.docChanges is not a function
I think I simply cannot run docChanges() on a single doc listener. In that case, how to view changes for a single firestore doc realtime listener then?
No, the API will not indicate to you what data or fields changes between snapshots. You just get a callback every time something changed anywhere in the document. You have to compare the previous and current snapshot to figure that out for yourself.
This docChanges() method is applicable on listeners being run on the collection rather than documents, so if you want to check changes between the snapshot put the docChanges() after the collection reference.

Flutter & Firebase: How to populate an array and then later, return all the contents

I have been trying to get arrays working in Firebase, and I am aware that there are a lot of references and discussions about this online, and I have read through all of these and none of it works.
First off, the Firebase side. The structure containing the array and two example strings inside it:
Firebase Structure
collection -> document -> fields
userData profileImages URLs (array)
: https://firebasestorage.googleapis.com/v0/b/app-138804.appspot.com/o/jRwscYWLs1DySLMz7jn5Yo2%2Fprofile%2Fimage_picker4459623138678.jpg?alt=media&token=ec1043b-0120-be3c-8e142417
: https://firebasestorage.googleapis.com/v0/b/app-138804.appspot.com/o/jRwscYWLs3872yhdjn5Yo2%2Fprofile%2Fimage_picker445929873mfd38678.jpg?alt=media&token=ec3213b-0120-be9c-8e112632
The first issue I am facing is writing to this array in the database:
Firestore.instance.collection('userData').document('profileImages').updateData({
'URLs': _uploadedFileURL,
});
Whenever I add data to this array, it just overwrites the existing data. I need to be able to keep all the existing data intact and simply add the current new line to the array.
Once this is working, I then need to be able to return all of the strings in this array without needing to know how many of them there will be.
For this part, I basically have nothing at this point. I could show some of the things I have tried based on suggestions from other articles on this, but none of it is even close to working correctly.
im assuming that _uploadedFileURL is a String, and you are updating the property URLs, that's why your data gets overwritten, because you are changing the URLs value to a single string which is _uploadedFileURL. to solve this issue, simply get the current data inside profileImages before commiting the update. like so
final DocumentSnapshot currentData = await Firestore.instance.collection('userData').document('profileImages').get();
Firestore.instance.collection('userData').document('profileImages').updateData({
'URLs': [
...currentData.data['URLs'],
_uploadedFileURL
],
});
and for the second part of your question, all you need is to query for the profileImages
Future<List<String>> _getProfileImages() {
final document = Firestore.instance.collection('userData').document('profileImages').get();
return document.data['profileImages]
}
the result of the get method will be a DocumentSnapshot, and inside the data property will access the profileImages which is a List<String>.
Ok guys and girls I have worked this out. Part 1: appending data to an array in Firebase.
Firestore.instance.collection('userData').document('profileImages').updateDataupdateData({
'URLs':FieldValue.arrayUnion([_uploadedFileURL]),
});
Where _uploadedFileURL is basically a string, for these purposes. Now I have read that arrayUnion, which is super groovy, is only available in Cloud Firestore, and not the Realtime Database. I use Cloud Firestore so it works for me but if you are having issues this might be why.
Now what is extra groovy about Cloud Firestore is that you can similarly remove an element from the array using:
Firestore.instance.collection('userData').document('profileImages').updateDataupdateData({
'URLs':FieldValue.arrayRemove([_uploadedFileURL]),
});
So how to get this data back out again. A simple way I have found to get that data and chuck it into a local array is like so:
List imageURLlist = [];
DocumentReference document = Firestore.instance.collection('userData').document('profileImages');
DocumentSnapshot snapshot = await document.get();
setState(() {
imageURLlist = snapshot.data['URLs'];
});
From here at least you have the data, can add to it, can remove from it and this can be a platform for you to figure out what you want to do with it.

Firestore default users data initialization

I am creating a game and I want to store completed game levels on firestore for each user.
Now my problem is that I will have to initalize this data once - I want to add a document for new user and a pojo that containts map of level ids and boolean for completed/uncompleted.
So I need to execute some kind of logic like "if document with this id doesnt exist, then add that document and add default data that means user hasnt completed any levels". Is there some way that would guarantee Id have to execute this logic only once? I want to avoid some kind of repeating/re-try if something fails and so on, thanks for your suggestion
That's what a transaction is for (definitely read the linked docs). In your transaction, you can read the document to find out if it exists, then write the document if it does not.
Alternatively, you may be able to get away with a set() with merge. A merged set operation will create the document if it doesn't exist, then update the document with the data you specify.
The typical approach to create-a-document-if-it-doesn't-exist-yet is to use a transaction. Based on the sample code in the documentation on transactions:
// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");
return db.runTransaction(function(transaction) {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(sfDocRef).then(function(sfDoc) {
if (!sfDoc.exists) {
transaction.set(sfDocRef, { count: 1 });
}
});
}).then(function() {
console.log("Transaction successfully committed!");
}).catch(function(error) {
console.log("Transaction failed: ", error);
});
Also see:
Firestore create document if it doesn't exist security rule, which shows security rules that allow a document to be create-but-not-updated.
Create a document only if it doesn't exist in Firebase Firestore, which shows how to allow a document only to be created by a UID identified in the data.
the documentation on transactions
the reference docs for the Transaction class

Resources