Check if a document exists - android

I would like to check if a document exists in my collection "users" because when I try this code, it seems not to enter in the OnCompleteListener when the document doesn't exist (when no documents have a correct value with the field "user_uid") :
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("users").whereEqualTo("user_uid", user.getUid())
.limit(1).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
} else {
Log.w(TAG, "Error getting documents.", task.getException());
}
}
});
When I have a document with my condition whereEqualTo("user_uid", user.getUid()), it's working.
So, how can I check if there is a document where the field "user_uid" == user.getUid()?

I copied your code, and tried your implementation of OnCompleteListener with limit(1). It worked with me on both scenarios where there are results, and no results.
Firestore has a way to check if there is no results by checking if it isEmpty() or not, before performing any operations. Because performing any operation will cause IndexOutOfBoundsException most of the time with iterating on getDocuments().
for example:
if (task.isSuccessful()) {
boolean isEmpty = task.getResult().isEmpty();
}
I hope this will help you.

Related

Firestore: Why is my code not calling "if (!document.exists())"?

My intention here is to display a Toast message if the user inputs an email/password combination that does not match any of the user profiles in the Firestore database.
I've been trying this multiple ways, but it refuses to call the code within "if (!document.exists()))." I've tried omitting "(!document.exists) and just using "else" - nothing.
Everything else works great. "if (document.exists())" happily returns the info and logs in. Please let me know if I need to include more info!
FirestoreRepository:
public void queryUserByEmailPassword(String email, String password) {
Query userQuery = userColRef.whereEqualTo("email", email).whereEqualTo("password", password);
userQuery.get().addOnCompleteListener(querySnapshotOnCompleteListener);
}
LoginViewModel:
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot doc : task.getResult()) {
if (!document.exists()) { // this code will NOT execute
Log.d(TAG, "Error getting documents: ", task.getException());
return;
} else { // this code executes flawlessly
user = doc.toObject(User.class);
currentUser.setValue(user);
doesUserExist.setValue(true);
repo.signIn(user.getEmail(), user.getPassword());
}
}
}
}
It looks like you're assuming that an error with the query will result in a document to show up in a QueryDocumentSnapshot. That's not the way it works. If there's an error with the query, then task.isSuccessful() will return false. You're currently not checking that case.
If you query returns no documents, then your for loop will not execute at all. It is not considered an "error" to get zero documents. If you need to know if there are no documents in the result, you should check that the QuerySnapshot contains no documents:
if (task.isSuccessful()) {
QuerySnapshot qs = task.getResult();
if (qs.size() == 0) {
// the query returned no documents - decide what to do
}
else {
// iterate the documents here
for (QueryDocumentSnapshot snapshot : qs) {
}
}
}
else {
// there was an error, use task.getException() to figure out what happened
}

firestore onSuccess listener isn't working

I am trying to download some Quiz objects from my database.
The following function is called from onCreate of a certain activity.
private void downloadQuizzesFromCloud(){
String user_id = FirebaseAuth.getInstance().getCurrentUser().getUid();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
String user_quizzes_path = "users/".concat(user_id).concat("/quizzes");
Query userQuizzes = db.collection(user_quizzes_path);
userQuizzes.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
quizzes.clear();
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
Toast.makeText(QuizzesActivity.this,"downloaded to list ".concat(String.valueOf(quizzes.size()).concat(" quizzes")), Toast.LENGTH_SHORT).show();
//TODO put in recycle adapter
} else { }
}
});
}
(user_quizzes_path contains the correct path to a collection of Quiz objects stored on the cloud)
I debugged this functions and found out that after the command:
userQuizzes.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>()
The function finishes execution, that is the onComplete cases aren't checked and executed and all this code is just skipped.
I tried to find this on the documentation of firebase but didn't find anything.
Why is this happening and how can I fix this?
Would appreciate some help here, thanks!
The onComplete is called when the read operation has completed from the Firestore servers. If it's not getting called, I can see two possible reasons:
You're not connected to the server. Unless you've read the data before (and it's in the local database that the Firestore client maintains), this means the read never completes locally.
You're not thinking asynchronously. Note that data is read from the server asynchronously, and there may be some time between when you call get() and when onComplete fires. To test if this is the case, put a breakpoint on if (task.isSuccessful()) { and run the app in the debugger. The breakpoint will hit when the data is read from the server.
Use a callback interface. Just like this below.
private void downloadQuizzesFromCloud(Consumer listener) {
String user_id = FirebaseAuth.getInstance().getCurrentUser().getUid();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
String user_quizzes_path = "users/".concat(user_id).concat("/quizzes");
Query userQuizzes = db.collection(user_quizzes_path);
userQuizzes.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<Quiz> quizzes = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
listener.onGet(quizzes);
Toast.makeText(QuizzesActivity.this,"downloaded to list ".concat(String.valueOf(quizzes.size()).concat(" quizzes")), Toast.LENGTH_SHORT).show();
//TODO put in recycle adapter
} else { }
}
});
}
interface Consumer {
void onGet(List<Quiz> quizzes);
}

firestore read is asynchronous and I want synchronous behaviour

I want to store locally the data I am reading from the cloud.
To achieve this I am using a global variable(quizzes) to hold all the data.
For this, when I am building my Quiz objects, I need to make sure that before I am creating them, the relevant data has been already downloaded from the cloud. Since when reading data from firestore, it happens asynchronously.
I didn't enforced this (waiting for the read to finish) before -I just used onSuccess listeners, and I encountered synchronization problem because the reading tasks weren't finished before I created my Quiz objects with the data from the cloud.
I fixed this with a very primitive way of "busy waiting" until the read from the cloud is complete. I know this is very stupid, a very bad practice, and making the application to be super slow, and I am sure there is a better way to fix this.
private void downloadQuizzesFromCloud(){
String user_id = FirebaseAuth.getInstance().getCurrentUser().getUid();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
CollectionReference quizzesRefrence = db.collection("users").document(user_id).collection("quizzes");
Task<QuerySnapshot> task = quizzesRefrence.get();
while(task.isComplete() == false){
System.out.println("busy wait");
}
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
}
I looked online in the documentation of firestore and firebase and didn't find anything that I could use. (tried for example to use the "wait" method) but that didn't help.
What else can I do to solve this synchronization problem?
I didn't understand if you tried this solution, but I think this is the better and the easier: add an onCompleteListener to the Task object returned from the get() method, the if the task is succesfull, you can do all your stuff, like this:
private void downloadQuizzesFromCloud(){
String user_id = FirebaseAuth.getInstance().getCurrentUser().getUid();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
CollectionReference quizzesRefrence = db.collection("users").document(user_id).collection("quizzes");
quizzesRefrence.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccesful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
}
});
}
}
In this way, you'll do all you have to do (here the for loop) as soon as the data is downloaded
You can make your own callback. For this, make an interface
public interface FireStoreResults {
public void onResultGet();
}
now send this call back when you get results
public void readData(final FireStoreResults){
db.collection("users").document(user_id).collection("quizzes")
.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
results.onResultGet();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
results.onResultGet();
}
});
}
Now in your activity or fragment
new YourResultGetClass().readData(new FireStoreResults(){
#Override
public void onResultGet() {
new YourResultGetClass().getQuizzes(); //this is your list of quizzes
//do whatever you want with it
}
Hope this makes sense!

Firestore Query chaining using AND

How to chain the query parameters for Firestore. I want to dynamically add query parameters (along with some common ones). But it does not seem to be working. Is this a firestore limitation?
Chaining in the same line works:
db.collection("MY_COLLECTION")
.whereEqualTo("user.firebaseUserId" , FirebaseAuth.getInstance().getUid())
.whereEqualTo("formId",formId)
.whereEqualTo("user.active","true")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
FirestoreResponse response = new FirestoreResponse();
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
//getting results here - works!
} else {
}
}
});
But trying to add the conditions on the query object reference returns results only based on the 1st condition specified:
Query firebaseQuery = collectionReference. whereEqualTo("user.firebaseUserId" , "myuserId"); //only this condition is applied
firebaseQuery.whereEqualTo("user.active","true");
if(someCondition){
firebaseQuery.whereEqualTo("user.smart","true");
}
firebaseQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
FirestoreResponse response = new FirestoreResponse();
if (task.isSuccessful()) {
//returns results only based on the 1st condition !!
} else {
}
}
});
This is strange since the .whereEqualTo returns a Query object.
I have also tried using CollectionReference.get() - along with adding query before to CollectionReference.
I figured out the issue - I was reusing the same query object "firebaseQuery " and calling whereEqualTo on the same object.
The whereEqualTo needs to be called on the query object from previous step instead of using the first query ref.
Query firebaseQuery1 = db.collection("MY_COLLECTION")
.whereEqualTo("user.firebaseUserId" , "someUserId");
Query firebaseQuery2 = firebaseQuery1.whereEqualTo("formId",formId);
Query firebaseQuery3 = firebaseQuery2.whereEqualTo("user.active","true");
firebaseQuery3.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
FirestoreResponse response = new FirestoreResponse();
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
works now !!
//}
} else {
}
}
});

Cloud Firestore: retrieving cached data via direct get?

Retrieving data from the server may take some seconds. Is there any way to retrieve cached data in the meantime, using a direct get?
The onComplete seems to be called only when the data is retrieved from the server:
db.collection("cities").whereEqualTo("state", "CA").get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
...
}
}
});
Is there any callback for the cached data?
Now it is possible to load data only from cached version. From docs
You can specify the source option in a get() call to change the default behavior.....you can fetch from only the offline cache.
If it fails, then you can again try for the online version.
Example:
DocumentReference docRef = db.collection("cities").document("SF");
// Source can be CACHE, SERVER, or DEFAULT.
Source source = Source.CACHE;
// Get the document, forcing the SDK to use the offline cache
docRef.get(source).addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
// Document found in the offline cache
DocumentSnapshot document = task.getResult();
Log.d(TAG, "Cached document data: " + document.getData());
} else {
Log.d(TAG, "Cached get failed: ", task.getException());
//try again with online version
}
}
});
I just ran a few tests in an Android app to see how this works.
The code you need is the same, no matter if you're getting data from the cache or from the network:
db.collection("translations").document("rPpciqsXjAzjpComjd5j").get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
DocumentSnapshot snapshot = task.getResult();
System.out.println("isFromCache: "+snapshot.getMetadata().isFromCache());
}
});
When I'm online this prints:
isFromCache: false
When I go offline, it prints:
isFromCache: true
There is no way to force retrieval from the cache while you're connected to the server.
If instead I use a listener:
db.collection("translations").document("rPpciqsXjAzjpComjd5j").addSnapshotListener(new DocumentListenOptions().includeMetadataChanges(), new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(DocumentSnapshot snapshot, FirebaseFirestoreException e) {
System.out.println("listen.isFromCache: "+snapshot.getMetadata().isFromCache());
}
}
);
I get two prints when I'm online:
isFromCache: true
isFromCache: false
You can disable network access and run the query to access data from cache .
For firestore :
https://firebase.google.com/docs/firestore/manage-data/enable-offline#disable_and_enable_network_access
For firebase database call db.goOffline() and db.goOnline()

Categories

Resources