Getting current user's data from firebase firestore in flutter - firebase

I want to get data from firestore, but I can't seem to do it properly and it always returns null. Here's what I tried:
Map<String, dynamic>? userMap2;
void getCurrentUser() async {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
final User? user = _auth.currentUser;
final uuid = user!.uid;
setState(() {
isLoading = true;
});
await _firestore
.collection('users')
.where("uid", isEqualTo: uuid)
.get()
.then((value) {
setState(() {
userMap2 = value.docs[0].data();
isLoading = false;
});
print(userMap2);
});
}
and when I try to use that data, I try to use it like this: userMap2!['firstName']

Try to put user data in document and then use,
_firestore
.collection('users')
.doc(uuid)
.get().then((value) {
setState(() {
userMap2 = value.docs[0].data();
isLoading = false;
});
print(userMap2);
});

In React, calling setState is an asynchronous operation. In addition, loading data from Firestore is an asynchronous operation. This means that in your current code, the print(userMap2) runs before your then callback is called, and even further before the userMap2 = value.docs[0].data() has been completed.
I recommend not combining then with await, and doing:
const value = await _firestore // 👈
.collection('users')
.where("uid", isEqualTo: uuid)
.get();
setState(() {
userMap2 = value.docs[0].data();
isLoading = false;
});
print(value.docs[0].data()); // 👈
On the first line I marked, we're now taking the return value from the awaited get(), so that we no longer need a then block. This handles the asynchronous nature of the call to Firestore.
Then on the second marked line, we print the value directly from the results from the database, instead of from the setState call. This addresses the asynchronous nature of calling setState.
For more on these, see:
Why is setState in reactjs Async instead of Sync?
Reactjs setState asynchronous (which shows how to pass a second argument to setState that is run when the state has been set).

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

Using Transactions but still getting same objects from Firebase

I have a Firebase document "coupon" that has 2 fields inside: an array of strings and an integer as seen below
Currently if a user clicks on a button to get a coupon, it will remove the 0 index at Firebase and show that removed array as coupon code in a Text widget, but if two or more users click on the button at the same time they all get the same string from the array.
This is my button on click code currently:
try {
await FirebaseFirestore.instance
.runTransaction((transaction) async {
DocumentReference
couponCollectionReference =
FirebaseFirestore.instance
.collection('coupons')
.doc(widget.couponNumber);
DocumentReference userCollectionReference =
FirebaseFirestore.instance
.collection('users')
.doc(getUserID());
setState(() {
couponTitle = couponCode[0];
couponBlur = 0.0;
isButtonWorking = false;
});
transaction
.update(couponCollectionReference, {
'coupon_code': FieldValue.arrayRemove(
[couponCode[0]]),
});
transaction
.update(couponCollectionReference, {
'coupons_available':
FieldValue.increment(-1),
});
transaction
.update(userCollectionReference, {
'current_points':
FieldValue.increment(-100),
});
await screenshotController
.capture(
pixelRatio: 2,
delay: Duration(milliseconds: 20))
.then((capturedImage) async {
ShowCapturedWidget(
context, capturedImage!);
}).catchError((onError) {
print(onError);
});
});
} catch (e)
Are transactions the way to go and I'm just not implementing them right or am I using a totally wrong approach ?
In order for the coupon document to be considered part of the transaction, you have to read it from the database through the transaction object (and use the value of coupons from there).
In your code that'd be something like this:
await FirebaseFirestore.instance
.runTransaction((transaction) async {
DocumentReference
couponCollectionReference =
FirebaseFirestore.instance
.collection('coupons')
.doc(widget.couponNumber);
DocumentSnapshot couponDoc = await transaction.get(couponCollectionReference); // 👈
couponCode = (couponDoc.data() as Map<String, dynamic>)['coupons'];
...

Flutter :How to pass List to Firestore where condetion

How to pass a List as param to firestore where condition? To get all data meets the condition
ie:-
List<String> topicList ;
getData() async {
SharedPreferences pref = await SharedPreferences.getInstance();
topicList = pref.getStringList("topicSymbol");
}
Stream<QuerySnapshot> getStreamQuery(String collPath) {
List<String> filters ;
return db
.collection('$collPath')
.where('Symbol', isEqualTo: getData()) // <---- the problem is here
.snapshots();
}
Thank you in advance
There are a few problems with this approach: firstly getData is an async function and you are not awaiting to get the return list from get data. You want to call await getData() before the return call in getStreamQuery.
Secondly, you should use .where('Symbol', isEqualTo: topicList) in order to compare the lists (this is because getData() returns null)
Let me know if this works for you!
Since you're using await in there, your getData() actually returns a Future - and you're failing to deal with that when calling it.
The simplest fix is to make getStreamQuery async too:
getData() async {
SharedPreferences pref = await SharedPreferences.getInstance();
return pref.getStringList("topicSymbol");
}
Future<Stream<QuerySnapshot>> getStreamQuery(String collPath) async {
List<String> filters ;
return db
.collection('$collPath')
.where('Symbol', isEqualTo: await getData())
.snapshots();
}
When you call getStreamQuery, you'll need to use await there tool.

Future Function Returning Null Firestore

I am trying to make a function in order to see which method the user has used to sign in eg. Google. I made a function which was called getProvider(). Here is the code for the function.
Future<String> getProvider (FirebaseAuth _auth)
async {
var user = await _auth.currentUser();
var provider;
Firestore firestore = Firestore.instance;
await firestore
.collection('Users')
.document('${user.uid}')
.get()
.then((value) {
provider =
value.data['Authentication Provider'];
return provider;
});
}
However, when I print the output value of the function outside the function itself, I get null.
print(await getProvider(_auth)) //This prints null
However, when I print the value of provider inside the function, it is not null and In this case, I get google. When I removed this function and called all of the code wherever I needed it, it worked as expected.
This shows that there is a problem with the return value of this getProvider() function. Can someone please let me know how I can fix this so that it actually returns the correct value?
Try this:
Future<String> getProvider(FirebaseAuth _auth) async {
var user = await _auth.currentUser();
Firestore firestore = Firestore.instance;
return firestore
.collection('Users')
.document('${user.uid}')
.get()
.then((value) => value.data['Authentication Provider']);
}
then() returns a Future which you can later await on to get the value. Since your method are already returning Future<String> you should be able to just return the Future generated by then().

Flutter wait for Firestore to complete

I am using flutter with firebase to create an app. The function below gets some data from my firebase database, but the problem is that it takes too long to get the data. How can make sure the app waits for the firebase getting data to finish first before proceeding?
I would like to do something like an await, but I don't know if I can do:
await Firestore.instance...
Code:
void getData() {
Firestore.instance
.collection('collection')
.document('document')
.get()
.then((DocumentSnapshot ds) {
var count = ds.data.length;
for(var i = 0; i < count; i ++){
Firestore.instance
.collection('collection')
.document('document')
.get()
.then((DocumentSnapshot dss) {
// do something
});
}
});
}
To use await, you have to make your function async :
Future<void> getData() async {
Then yes, you can do
var result = await Firestore.instance.collection...
instead of handling the Future result in the then() callback. The next line will not be executed until the Future is resolved.
You will need to await for the firestore to return data.
and then return it.
Future getData() async {
DocumentSnapshot ds = await Firestore.instance
.collection('collection')
.document('document')
.get();
final data = ds.//do something with document snapshot
return data;
}
Hope this helps.

Resources