How to get a field value from a Document in Firestore? - firebase

So, I have learned how to create and update documents in a firebase firestore cloud, however I am having trouble reading data. Attached is my code for finding the value of the 'photourl' field:
String photoy;
Firestore.instance.collection('userdata').document('sepDGexTRuRkpx1WQYylDDmUI573')
.get().then((DocumentSnapshot ds){
photoy=ds.data['photourl'];
});
setState(() {
photourldisplay=photoy;
});
However, upon running my program, the photourldisplay value seems to not have changed and remains null upon running. This means that something is askew with my code that retrieves this "photourl" field. Can someone help me retrieve a field in a firebase document?

photoy does not contain the value you expect because Firestore queries are asynchronous. The get() returns immediately, and the callback is invoked some time later, after the query completes. There is no guarantee how long a query might take. If you want to pass the value of photoy to something else, you will have to wait until the callback completes by making use of it only within that callback.
Firestore.instance.collection('userdata').document('sepDGexTRuRkpx1WQYylDDmUI573')
.get().then((DocumentSnapshot ds){
photoy=ds.data['photourl'];
setState(() {
photourldisplay=photoy;
});
});

Your code is good you just have to await for the result:
void yourVoid () async {
String photoy;
await Firestore.instance.collection('userdata').document('sepDGexTRuRkpx1WQYylDDmUI573')
.get().then((DocumentSnapshot ds){
photoy=ds.data['photourl'];
});
setState(() {
photourldisplay=photoy;
});
}
EDIT:
as #Doug Stevenson said, there is two propers solutions:
void yourVoid () async {
DocumentSnapshot ds = await Firestore.instance.collection('userdata').document('sepDGexTRuRkpx1WQYylDDmUI573')
.get();
String photoy = ds.data['photourl'];
setState(() {
photourldisplay=photoy;
});
}
and:
Firestore.instance.collection('userdata').document('sepDGexTRuRkpx1WQYylDDmUI573')
.get().then((DocumentSnapshot ds){
photoy=ds.data['photourl'];
setState(() {
photourldisplay=photoy;
});
});

Related

How to await inside a stream while querying data from Firebase firestore

For context I'm using Getx state management for flutter and i need to call list.bindStream(availabilityStream()) on my Rx<List<Availability>> object.
here is my availabilityStream method
static Stream<List<Availability>> availabilityStream() {
return FirebaseFirestore.instance
.collection('availability')
.where('language',
isEqualTo: GetStorageController.instance.language.value)
.snapshots()
.map((QuerySnapshot query) {
List<Availability> results = [];
for (var availablity in query.docs) {
availablity["cluster"].get().then((DocumentSnapshot document) {
if (document.exists) {
print("Just reached here!");
//! Ignore doc if cluster link is broken
final model = Availability.fromDocumentSnapshot(
availabilityData: availablity, clusterData: document);
results.add(model);
}
});
}
print("result returned");
return results;
});
}
the cluster field on my availability collection is a reference field to another collection. The problem here is i need to await the .get() call to my firestore or the function returns before the data gets returned. I can't await inside the map function or the return type of Stream<List> changes. so how can i await my function call here?
using the advice i got from the comments I've used Stream.asyncMap to wait for all my network call futures to complete.
Here is my updated Repository
class AvailabilityRepository {
static Future<Availability> getAvailabilityAndCluster(
QueryDocumentSnapshot availability) async {
return await availability["cluster"]
.get()
.then((DocumentSnapshot document) {
if (document.exists) {
//! Ignore doc if cluster link is broken
final model = Availability.fromDocumentSnapshot(
availabilityData: availability, clusterData: document);
return model;
}
});
}
static Stream<List<Availability>> availabilityStream() {
return FirebaseFirestore.instance
.collection('availability')
.where('language',
isEqualTo: GetStorageController.instance.language.value)
.snapshots()
.asyncMap((snapshot) => Future.wait(
snapshot.docs.map((e) => getAvailabilityAndCluster(e))));
}
}
How i think this works is that the normal .map function returns multiple promises form the getAvailabilityAndCluster() method then all of the processes that execute asynchronously are all put to Future.wait() which is one big promise that waits all the promises inside it to complete. Then this is passed onto .asyncMap() which waits for the Future.wait() to complete before continuing with its result.

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,
});

I want to get document id from my firestore

I can't get document id from firestore check out the screenshot, you will understand better.
Future updateUserData(String name, String address, String description,
String image, String rating) async {
return await collectionReference.document(uid).setData({
'name': name,
'address': address,
'description': description,
'image': image,
'rating': rating,
'berberid' : ######, // what should I write here
});
}
Instead of null I want to pass that id
Couple of method I already tried
This is returning null
String docid() {
String id;
collectionReference.getDocuments().then((value) => {
value.documents.forEach((element) {
id = element.documentID;
}),
});
return id;
}
This returns the name of collection, such as berbers
documentReference.documentID
Data is loaded from Firebase asynchronously. That means that in this code:
String id;
collectionReference.getDocuments().then((value) =>{
value.documents.forEach((element) {
id= element.documentID;
}),
});
return id;
}
By the time the return id statement executes, the id= element.documentID hasn't run yet. You can most easily check this for yourself by putting breakpoints on these lines and running the code in the debugger, or by adding some log statements:
print("Before getDocuments()");
collectionReference.getDocuments().then((value) =>{
print("Got documents");
});
print("Aftter getDocuments()");
When you run this code, it prints:
Before getDocuments()
After getDocuments()
Got documents
This is likely not what you expected, but does explain why you get null from the function: the data hasn't loaded yet by the time your function exits.
This is completely normal when dealing with asynchronous operations, which is pretty much any modern web/cloud API. So you'll have to deal with it in your code.
The quickest way to do that is to move the code that needs the data into the then() callback that gets the data. So instead of return id you actually put the code that uses the id into the method:
collectionReference.getDocuments().then((value) =>{
String id;
value.documents.forEach((element) {
id= element.documentID;
}),
print(id); // use the id here
});
This could also be a call to your updateUserData function.
Alternatively, you can use async / await and return a Future from your function:
Future<string> getDocumentId() async {
String id;
var value = await collectionReference.getDocuments();
value.documents.forEach((element) {
id= element.documentID;
}),
return id;
}
To call this function you then do:
getDocumentId().then((id) =>{
print(id); // use id here
})
Or you can use another await and do:
var id = await getDocumentId()
print(id);
Here's what I think you should do, you add the documentID to barberid when you create the document instead of updating it which is weird how the user know ?. Here's how you do it:
await Firestore.instance.collection("your collection name").add({
'name': name,
'address': address,
'description': description,
'image': image,
'rating': rating,
}).then((value) => Firestore.instance
.document(value.path)
.updateData({"barberid": value.documentID}));
value there is DocumentReference that's how you get the documentID and then you can update it with Firestore.instance.document("document path") fill the document path with value.path, and update the barberid with documentID should work just fine.

Flutter Return Length of Documents from Firebase

Im trying to return the length of a list of documents with this function:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
respectsQuery.getDocuments().then((data) {
var totalEquals = data.documents.length;
return totalEquals;
});
}
I'm initialize this in the void init state (with another function call:
void initState() {
totalLikes(postID).then((result) {
setState(() {
_totalRespects = result;
});
});
}
However, when this runs, it initially returns a null value since it doesn't have time to to fully complete. I have tried to out an "await" before the Firestore call within the Future function but get the compile error of "Await only futures."
Can anyone help me understand how I can wait for this function to fully return a non-null value before setting the state of "_totalRespsects"?
Thanks!
I think you're looking for this:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
var querySnapshot = await respectsQuery.getDocuments();
var totalEquals = querySnapshot.documents.length;
return totalEquals;
}
Note that this loads all documents, just to determine the number of documents, which is incredibly wasteful (especially as you get more documents). Consider keeping a document where you maintain the count as a field, so that you only have to read a single document to get the count. See aggregation queries and distributed counters in the Firestore documentation.
Perfect code for your problem:
int? total;
getLength() async {
var getDocuments = await DatabaseHelper.registerUserCollection
.where("register", isEqualTo: "yes")
.get();
setState(() {
total = getDocuments.docs.length;
});
}
#override
void initState() {
super.initState();
getLength();
if (kDebugMode) {
print(total);
}
}

Firestore transaction fails with "Transaction failed all retries"

I'm running a very simple Firestore transaction which checks for the presence of a document, before writing to it if absent.
(The use case is registering a username - if it's not already registered, the current user gets to grab it)
Here's a snippet of the relevant Flutter code:
DocumentReference usernameDocRef =
Firestore.instance.collection(_USERNAMES).document(username);
await Firestore.instance.runTransaction((transaction) async {
var snapshot = await transaction.get(usernameDocRef);
if (!snapshot.exists) {
transaction.set(usernameDocRef, {
_UsernamesKey.userid: _user.id,
});
}
});
This is failing with an exception "Transaction failed all retries".
Based on the Firestore documentation, failure can occur for two reasons:
The transaction contains read operations after write operations. Read operations must always come before any write operations.
The transaction read a document that was modified outside of the transaction. In this case, the transaction automatically runs again. The transaction is retried a finite number of times.
I don't think I trigger either of those. Any suggestions?
The example transaction in the documentation uses await on its call to update. Perhaps you need the same on your call to set:
await Firestore.instance.runTransaction((transaction) async {
var snapshot = await transaction.get(usernameDocRef);
if (!snapshot.exists) {
await transaction.set(usernameDocRef, {
_UsernamesKey.userid: _user.id,
});
}
});
Firstly, try using the reference from the fresh snapshot and not from the original document reference. If this doesn't work, try changing [set] to [update] as I remember having the same error as you have experience now.
DocumentReference usernameDocRef =
Firestore.instance.collection(_USERNAMES).document(username);
await Firestore.instance.runTransaction((transaction) async {
var snapshot = await transaction.get(usernameDocRef);
if (!snapshot.exists) {
await transaction.update(snapshot.reference, {
_UsernamesKey.userid: _user.id,
});
}
});
This has recently been fixed - https://github.com/flutter/plugins/pull/1206.
If you are using the master channel, the fix should be available already. For the other channels (dev, beta, stable) YMMV.
I'm not a Flutter/Dart expert, but I expect you have to return something from within the transaction, so that Firestore knows when you're done:
await Firestore.instance.runTransaction((transaction) async {
var snapshot = await transaction.get(usernameDocRef);
if (!snapshot.exists) {
return transaction.set(usernameDocRef, {
_UsernamesKey.userid: _user.id,
});
}
})

Resources