Using Transactions but still getting same objects from Firebase - 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'];
...

Related

I can't fetch data from two different collection consecutively in Firebase with Flutter

I was trying to fetch from two different collection but I got a weird situation. First, I want to fetch a userID from posts collection. Then with that userID, I want to fetch data from users collection.
So, when I fetch from only the posts collection, print command works perfectly fine and prints the userID.
But when I add the users fetch statement that I showed in the code below it doesn't fetch it and shows an empty string (''), and users collection sends an error because I couldn't search the userID. What am I missing here?
class _ProductDetail extends State<ProductDetail> {
String getTitle = '';
String getLocation = '';
String getPrice = '';
String getImage = '';
String getUniversity = '';
String getProfileImage = '';
String getUserName = '';
String getSellerUserID = '';
#override
Widget build(BuildContext context) {
FirebaseFirestore.instance
.collection('posts')
.doc(widget.postID)
.get()
.then((incomingData) {
setState(() {
getTitle = incomingData.data()!['title'];
getPrice = incomingData.data()!['price'];
getImage = incomingData.data()!['postImage'];
});
});
FirebaseFirestore.instance
.collection('posts')
.doc(widget.postID)
.get()
.then((incomingData) {
setState(() {
getSellerUserID = incomingData.data()!['userID'];
});
});
print(getSellerUserID); //statement that will print the userID
//////////////////////IF I DELETE THIS SECTION, IT PRINTS THE USER ID//////////////////
FirebaseFirestore.instance
.collection('users')
.doc(getSellerUserID)
.get()
.then((incomingData) {
setState(() {
getUserName = incomingData.data()!['username'];
getProfileImage = incomingData.data()!['profileImage'];
getUniversity = incomingData.data()!['university'];
getLocation = incomingData.data()!['location'];
});
});
///////////////////////////////////////////////////////////////////////////////////////////////
return Scaffold(
....... rest of the code
Since data is loaded from Firestore asynchronously, the code inside your then blocks is called (way) later then the line after the call to get().
To see this most easily, add some logging like this:
print("Before calling Firestore")
FirebaseFirestore.instance
.collection('posts')
.doc(widget.postID)
.get()
.then((incomingData) {
print("Got data")
});
print("After calling Firestore")
If you run this code, it'll print:
Before calling Firestore
After calling Firestore
Got data
This is probably not the order you expected, but does explain why your next load from the database doesn't work: the getSellerUserID = incomingData.data()!['userID'] line hasn't been run yet by that time.
For this reason: any code that needs the data from Firestore, needs to be inside the then (or onSnapshot) handler, be called from there, or be otherwise synchronized.
So the simplest fix is to move the next database call into the `then:
FirebaseFirestore.instance
.collection('posts')
.doc(widget.postID)
.get()
.then((incomingData) {
var sellerUserID = incomingData.data()!['userID'];
setState(() {
getSellerUserID = sellerUserID;
});
print(sellerUserID);
FirebaseFirestore.instance
.collection('users')
.doc(sellerUserID)
.get()
.then((incomingData) {
setState(() {
getUserName = incomingData.data()!['username'];
getProfileImage = incomingData.data()!['profileImage'];
getUniversity = incomingData.data()!['university'];
getLocation = incomingData.data()!['location'];
});
});
});

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

How do I fetch data from multiple documents in a Firebase collection

I'm getting the user's location then using that location to fetch documents containing that location then displaying the data within the documents on the page.
The structure of my Firebase Database is /posts/{userId}/userPosts/{postId}. I want it to search through all the posts (meaning through all the different postIds).
Future<void> initState() {
super.initState();
getListings();
}
getListings() async {
placemark = await getUserLocation();
getLocationListings(placemark);
}
getLocationListings(placemark) async {
setState(() {
isLoading = true;
});
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection('posts')
// .doc(userId)
// .collection('userPosts')
.where('location', isGreaterThanOrEqualTo: placemark.locality)
.get();
setState(() {
posts = snapshot.docs.map((doc) => Post.fromDocument(doc)).toList();
isLoading = false;
});
// print(placemark.locality);
}
When I run the code as it is (.doc(userId) and .collection('userPosts') commented out), the list List<Post> posts = []; remains empty. However, when I uncomment them and provide a userId, I get all the documents containing placemark.locality. How can I go round this?

Admin access with flutter - hide and show widget and button based on User right firebase

I am working to build an admin access to a client. Among the visibility I need to constraint is the visibility of the button.
When changing access to user to admin, the button is not appearing back. The dependent boolean condition is mentioned below.
bool _show = false;
void showFloationButton() {
setState(() {
_show = true;
});
}
void hideFloationButton() {
setState(() {
_show = false;
});
}
void userAdminAccess() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
if ( currentUser != null) {
Firestore.instance
.collection('Users')
.where('isAdmin', isEqualTo: true);
} return showFloationButton();
}
Your code looks like it wants to perform a query, but it is not actually doing so. It's just building a Query object. You have to use get() to actually make that query happen, and await the response:
var querySnapshot = await Firestore.instance
.collection('Users')
.where('isAdmin', isEqualTo: true)
.get();
if (querySnapshot.size > 0) {
// there is at least one document returned by this query
}
else {
// there are not matching documents
}
I suggest learning more about how to perform queries in the documentation.
Note that what you're doing is potentially very expensive. It seems to me that you should probably get a single document for the user, identified by their UID, and look for a field within that document. Getting all admins could incur a lot of reads unnecessarily.

How do I get the surrounding data related to my userId using flutter and firebase

While using flutter I am able to successfully get the UserId, however I want to be able get more user data (using the UserId)
Surrounding Information:
With the userId how would I go about printing the users; name bio, membership... etc?
Since you are using Realtime Database, then to get the other data, you can do the following:
db = FirebaseDatabase.instance.reference().child("Users");
db.once().then((DataSnapshot snapshot){
Map<dynamic, dynamic> values = snapshot.value;
values.forEach((key,values) {
print(values);
print(values["name"]);
});
});
First add a reference to node Users then use the forEach method to iterate inside the retrieved Map and retrieve the other values.
Try like this :
Future<dynamic> getWeightinKeg() async {
final DocumentReference document = Firestore.instance.collection('you_collection_name').document(user_id);
await document.get().then<dynamic>(( DocumentSnapshot snapshot) async {
final dynamic data = snapshot.data;
print(data['name'].toString())
//Do whatever you want to do with data here.
});
}
getUsers() async {
//here fbPath is reference to your users path
fbPath.once().then((user){
if(user.value !=null){
Map.from(user.value).forEach((k,v){
//here users is List<Map>
setState((){
users.add(v);
});
}
}
});
}
//Or
getUsers() async {
//here fbPath is reference to your users path
//and userListener is StreamSubscription
userListener = fbPath.onChildAdded.listen((user){
//here users is List<Map>
setState((){
users.add(Map.from(user.snapshot.value));
});
});
}
//and cancel in dispose method by calling
userListener.cancel();

Resources