Future Function Returning Null Firestore - firebase

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().

Related

In Flutter, using await stores data correctly, but not collection name and not using await does not store data correctly, but correct collection name

My current code uses:
var currentUID = await database.getCurrentUserID();
Running this function with await on this line of code stores data in Firestore with correct user ID but time is always set to 0:
Future<void> addUserTime() async {
var currentUID = await database.getCurrentUserID();
return await database.workoutCollection
.doc(currentUID.toString())
.set({
'Workout Time': format(duration),
})
.then((value) => print('Time added'))
.catchError((error) => print('Failed to add time to database'));
}
Without using await like the previous line of code like this:
var currentUID = database.getCurrentUserID();
Firestore shows this: This is the firebase output. Wrong UserID from Firebase Authentication, but time is always set to what the user logged:
Future<void> addUserTime() async {
var currentUID = database.getCurrentUserID();
return await database.workoutCollection
.doc(currentUID.toString())
.set({
'Workout Time': format(duration),
})
.then((value) => print('Time added'))
.catchError((error) => print('Failed to add time to database'));
}
This is my database class where I call the getCurrentUserID() function:
How can I get both the correct UID and correct time the user logged?
FirebaseAuth stores the current user once it's authenticated in FirebaseAuth.instance.currentUser, if we look into this property we will find that the type is User?, not Future<User?>, hence you don't need to await to get the currentUser, simply:
return FirebaseAuth.instance.currentUser?.uid;
Additionally, currentUser?.uid returns a String, so no need to call .toString().
Assuming that the duration is not 0, with these modifications the code should work, for further reference here's a DartPad example that updates a user record based on currentUser.uid.

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

Getting current user's data from firebase firestore in flutter

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).

Querying firestore to check if field value exist and Converting Stream into Future?

I have a function which i created to query firestore and checkwhether a phoneNo exist in the collection called 'users'
I want to get a boolean if the phone No already exist in a document field.
How can i do it
Future<bool> phoneRegisterCheck(phone) async{
bool phoneAlreadyRegistered;
print('start');
var result = Firebase.instance.collection('users').where('phoneNo', isEqualTo: phone);
}
I want to wait for the query to finish and then return the result.
When i Use listen method it is not happening right function returns null.
Future<bool> phoneRegisterCheck(phone) async{
bool phoneRegistered;
print('start');
var result = DatabaseService().userCollection.where('phoneNo', isEqualTo: phone);
result.snapshots().listen((val){
val.documents.isEmpty ? phoneRegistered=false:phoneRegistered=true;
});
return phoneRegistered;
}
Since streams are asynchronous then in your code the return statement will be executed before the data is fully retrieved therefore you get null. You should
use await for instead of listen:
Future<bool> phoneRegisterCheck(phone) async{
bool phoneRegistered;
print('start');
var result = DatabaseService().userCollection.where('phoneNo', isEqualTo: phone).snapshots();
await for(var values in result){
values.documents.isEmpty ? phoneRegistered=false:phoneRegistered=true;
}
return phoneRegistered;
}
From the docs:
Streams can be created in many ways, which is a topic for another article, but they can all be used in the same way: the asynchronous for loop (commonly just called await for) iterates over the events of a stream like the for loop iterates over an Iterable.
https://dart.dev/tutorials/language/streams

Retrieve Document content from Firebase in Flutter

I'm trying to retrieve user data from a Firebase Collection.
This is what it looks like:
This is the method I wrote to get the data:
static String getUserData(creatorId, keyword) {
var documentName = Firestore.instance
.collection('users')
.document(creatorId)
.get()
.then((DocumentSnapshot) {
String data = (DocumentSnapshot.data['$keyword'].toString());
return data;
});
}
The method only returns null. If I print the String in the Method it works. How can I return the String?
Help would be greatly appreciated.
Cheers Paul
You need to use async and await to be able to wait for the data to be fully retrieved and then you can return the data.
The async and await keywords provide a declarative way to define asynchronous functions and use their results.
For example:
Future<String> getUserData(creatorId, keyword) async {
var documentName = await Firestore.instance
.collection('users')
.document(creatorId)
.get()
.then((DocumentSnapshot) {
String data = (DocumentSnapshot.data['$keyword'].toString());
return data;
});
}
And then you since getUserData returns a Future, you can use the await keyword to call it:
await getUserData(id, key);
https://dart.dev/codelabs/async-await#working-with-futures-async-and-await

Resources