Firebase duplicates presumably because of offline behavior - firebase

I have an app (Flutter / Firebase) where people save their predictions for soccer matches.
The way it works is: every person has a document per round of soccer matches.
So when saving a prediction, I check if there is a document for that person for that round, if it does, I write into it, if it doesn't I create a new document.
It was working fine until the amount of users and their circumstances increased. Now, I'm getting a few duplicates and I'm guessing it's because the person saving picks has no internet connection so it doesn't find a document and tries to write a new one.
What would be the best approach to fix this?
Designing a specific reference path for the documents?
Checking for internet connection before attempting any writes?
Or something else?
Future<String> saveToFirebase() async {
return await Firestore.instance.collection('picks')
.add(this.toJson())
.then((value) => Future.value(value.documentID))
.catchError((onError) => Future.error(onError));
}
Future<DocumentReference> picksDocumentReferenceFor(User user, String round, String groupId, String leagueId, { bool create = false, String roundName, String seasonId }) async {
String path = 'picks/';
return Firestore.instance
.collection('picks')
.where('groupId', isEqualTo: groupId)
.where('userId', isEqualTo: user.id)
.where('round', isEqualTo: round).getDocuments()
.then((value) async {
String docPath;
if (value.documents.isEmpty) {
//No document found.. need to create it.
if (create) {
await PicksDocument().saveToFirebase()
.then((documentId) {
docPath = path + documentId;
});
}
} else {
docPath = path + value.documents.first.documentID;
}
print(docPath);
DocumentReference ref;
if (docPath != null) ref = Firestore.instance.document(docPath);
return Future.value(ref);
});
}

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'];
});
});
});

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 may I query 2 documents in Flutter Firestore and return as a List?

New to the world of Flutter and Dart.
I'm trying to Query from one collection (Sessions), with another collection of User, where each session can have only one user. I want to get each Session, add User Data and return to my Future<List < Sessions > >.
I have Models for both Sessions and the User, and I'm able to extract both the Sessions document and then being able to form my Sessions using the User info, however I have no luck in returning the correct data. It seems like im getting the query fine, however my returning data is coming out as a List< Future < Sessions > > instead of a List< Sessions >.
Sessions Model
Sessions(
{this.id,
this.title,
this.datetime,
this.userName,
this.userImage});
factory Sessions.fromJson(DocumentSnapshot doc, userName, userImage) {
String id = doc.documentID;
Map json = doc.data;
return Sessions(
id: id,
title: json['title'],
datetime: json['datetime'].toDate(),
userName: userName,
userImage: userImage,
);
}
Firebase Query
Future<List<Sessions>> getSessions() async {
// Getting Sessions
final result = await _liveSessionsRef
.where('isFeatured', isEqualTo: true)
.getDocuments();
// Querying from Users and returning Sessions with User Id and User Image
final data = result.documents.map((doc) async {
return await _userCollectionRef
.document(doc.data['id'])
.get()
.then(
(value) {
return Sessions.fromJson(doc, value.data['name'], value.data['image']);
},
);
}).toList();
}
return data;
}
Future<List<Sessions>> getSessions() async {
// Getting Sessions
final result = await _liveSessionsRef
.where('isFeatured', isEqualTo: true)
.getDocuments();
// Querying from Users and returning Sessions with User Id and User Image
final data = Future.wait(result.documents.map((doc) {
return _userCollectionRef
.document(doc.data['id'])
.get()
.then(
(value) {
return Sessions.fromJson(doc, value.data['name'], value.data['image']);
},
);
}).toList());
}
return data;
}

firestore query does not return documents inside flutter/dart mobile app, but works in javascript

Very strange behaviour I am experiencing with firestore.
Below dart code does not return the value
await Firestore.instance.collection('users')
.where("phoneNumber", isEqualTo: phoneNumber)
.getDocuments();
The javascript code from web returns the value
db.collection('users').where('phoneNumber', '==', 'xxxxxxxxxx').get().then((result) => {
console.log( result.docs.length )
}).catch((err) => {
console.log(err)
});
But I can clearly see the phone number does exist in the colection.
I just don't know if this is because of pending writes or cache. Where can I disable it if that is the case?
edit the code for phNumber
Future<User> getPhoneUser(String phoneNumber) async {
if (phoneNumber == 'xxxxxxxxxx') {
print('yes the phone number is same');
}
try {
QuerySnapshot qsnap = await usersRef
.where("phoneNumber", isEqualTo: phoneNumber)
.getDocuments();
int length = qsnap.documents.length;
if (length > 0) {
DocumentSnapshot doc = qsnap.documents[0];
doc.data['id'] = doc.documentID;
doc.data['createdAt'] = doc.data['createdAt'].toDate().toString();
doc.data['updatedAt'] = doc.data['updatedAt'].toDate().toString();
User user = User.fromJson(doc.data);
return user;
} else {
return null;
}
} catch (error) {
return null;
}
}
If you are running your flutter app on a simulator, sometimes the simulators go offline for some reason and you can no longer fetch data from the network.
If this happens, you can restart your simulator to bring the network connection back. I would suggest debugging with an actual physical device whenever possible.

A simple Query in flutter/firebase database

I try to experience Firebase Live database with flutter.
I just would like to get a value in the datasnapshot of the firebase response.
My Firebase
My Code
static Future<User> getUser(String userKey) async {
Completer<User> completer = new Completer<User>();
String accountKey = await Preferences.getAccountKey();
FirebaseDatabase.instance
.reference()
.child("accounts")
.child(accountKey)
.child("users")
.childOrderBy("Group_id")
.equals("54")
.once()
.then((DataSnapshot snapshot) {
var user = new User.fromSnapShot(snapshot.key, snapshot.value);
completer.complete(user);
});
return completer.future;
}
}
class User {
final String key;
String firstName;
Todo.fromJson(this.key, Map data) {
firstname= data['Firstname'];
if (firstname== null) {
firstname= '';
}
}
}
I got Null value for firstname.
I guess I should navigate to the child of snapshot.value. But impossible to manage with foreach, or Map(), ...
Kind regards, Jerome
You are querying with a query and the documentation for Queries (here in JavaScript, but it is valid for all languages), says that "even when there is only a single match for the query, the snapshot is still a list; it just contains a single item. To access the item, you need to loop over the result."
I don't know exactly how you should loop, in Flutter/Dart, over the children of the snapshot but you should do something like the following (in JavaScript):
snapshot.forEach(function(childSnapshot) {
var childKey = childSnapshot.key;
var childData = childSnapshot.val();
// ...
});
and assuming that your query returns only one record ("one single match"), use the child snapshot when you do
var user = new User.fromSnapShot(childSnapshot.key, childSnapshot.value);
This will give you Users in reusable dialog. There might be slight disservice to yourself if you don't use stream and stream-builders, the solution below is a one time fetch of the users' collection on FirebaseDB.
class User {
String firstName, groupID, lastName, pictureURL, userID;
User({this.firstName, this.groupID, this.lastName, this.pictureURL, this.userID});
factory User.fromJSON(Map<dynamic, dynamic> user) => User(firstName: user["Firstname"], groupID: user["Group_id"], lastName: user["Lastname"], pictureURL: user["Picturelink"], userID: user["User_id"]);
}
Future<List<User>> users = Firestore.instance.collection("users").snapshots().asyncMap((users) {
return users.documents.map((user) => User.fromJSON(user.data)).toList();
}).single;

Resources