FirebaseFirestore add() method being executed before validation (get() documents) [duplicate] - android

This question already has answers here:
How to return a DocumentSnapShot as a result of a method?
(2 answers)
Closed 2 years ago.
I'm building an APP and using Firestore to create a collections of Users. Before adding the new user to the collection i need to check within the collection if the email is already in use and for this i've built two methods: one for reading the collection looking for an user with that email and the other one to adding the new user IF everything is ok. But no matter what I do, the add method always executes first leading to the validation being useless. I guess it's has something to do with the methods priority withing Firebase but i really couldn't pull out with a solution
Here's the two methods
The first one it's validation and the second one it's the add
private boolean createFirestoreUser(final String identificador) {
final boolean[] isValid = {true};
FirebaseFirestore.getInstance().collection("Usuarios")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
for (QueryDocumentSnapshot document : task.getResult()) {
if(identificador.equals(document.getString("identificador")))
isValid[0] = false;
}
}
});
return isValid[0];
}
private void createUser(Usuario novoUsuario) {
FirebaseFirestore.getInstance().collection("Usuarios")
.add(novoUsuario)
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
#Override
public void onSuccess(final DocumentReference documentReference) {
documentReference
.update("id", documentReference.getId())
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
loginSessionManager.createLoginSession(documentReference.getId());
loginSessionManager.checkLogin(this.getClass());
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
snackbar.showMensagemLonga(v,e.getMessage());
}
});
}
The second one is ALWAYS being called first no matter the order i use on the button listener. I've debbuged and it really enters in the isValid[0] = false after the user is added

Where are you calling the methods?
You could just call the createUser inside of the .addOnSuccessListener this way it will not be called until the valdiation is returned.
Something like:
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
for (QueryDocumentSnapshot document : task.getResult()) {
if(identificador.equals(document.getString("identificador")))
isValid[0] = false;
else
createUser(novoUsuario)
}
}
The reason why I'm asking where you are calling the methods is because your variable might be by default true which would trigger the second function before the async listener is returned therefore calling the second method before the validation is made.
So the alternative would be to call the register method inside the same mehrod where you are validating or if what I'm assuming that you have a boolean declared first to see if you call the create method, just have it false as default and make sure to be calling it after the async .OnCompleteLister is finished.

Related

DocuemntSnapshot on Android is one result late [duplicate]

This question already has an answer here:
How to check a certain data already exists in firestore or not
(1 answer)
Closed 2 years ago.
I have an EditText where I type an username. After I click "REGISTER", a function processes the username to check if it already exists:
public boolean usernameExists (String username) {
database.collection("users").document(username).get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete (#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
usernameExists_result = task.getResult().exists();
}
else {
task.getException().printStackTrace();
}
}
});
return usernameExists_result;
}
where database and usernameExists_result are both fields. Of course, since the username is the documentID, this function should return true if the document (and so the username) exists, and false if it doesn't.
Now, the problem is the function - apart from the first call - always returns the value that it should have returned on the previous call.
For example, if on the first call the function returns true because the username already exists, and then I type another username that doesn't exist, the function will also return true. On the next call, it will return false, because that it what it should have returned on the previous call. And on the next call, it will return whatever it should have returned on the previous, and so on.
Does anyone have an explanation for this behavior? I can provide whatever info is needed.
Firebase is not working like this. Try to avoid making a methood boolean INSTEAD just just go on.
Example here:- your button
private void setBtnRegister(){
btnRegister.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view){
//Check all exception here. (incase editext is null)
//and then put the code here instead that method boolean
database.collection("users").document(username).get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete (#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
//Success, maybe go next step
}
else {
//Fail
}
}
});
}
});
}

Random document that doesnt exist in firestore gets selected on android and deleted

I am trying to delete a document from Firestore. I am trying to do this based on the task ID that was randomly generated by Firestone. when a particular task is selected on android, I want to be able to delete that task. However, when I tried debugging the code, it shows a random ID that doesn't exist on the database and tries to delete that, sending me a success message in the console. I am not sure where I am going wrong. Please advice.
public void deleteTasks(View v) {
userId = mFirebaseAuth.getCurrentUser().getUid();
String tskid= fStore.collection("usersData").document(userId).collection("tasks").document().getId();
DocumentReference taskref = fStore.collection("usersData").document(userId).collection("tasks").document(tskid);
taskref.delete().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Log.d("tag", "Task Deleted Successfully");
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("tag", "Task Deletion Unsuccessful");
}
});
}
the above delete method is called on the button using android:OnClick
The Task I'd like to delete is Circled
When you are using the following line of code:
String tskid= fStore.collection("usersData").document(userId)
.collection("tasks").document()
.getId();
You are generating a new random ID. Actully, you are reserving a key for a document that you'll be writing in the future. When using this line:
DocumentReference taskref = fStore.collection("usersData").document(userId)
.collection("tasks").document(tskid);
You are creating a reference to that location. However, when using this line:
taskref.delete().addOnCompleteListener(/* ... */);
You are trying to delete a document that does not exist and this is because you didn't create it in the first place. If you need to delete a specific document, you need to know the ID. So the following lines of code will do the trick:
public void deleteTasks(View v) {
userId = mFirebaseAuth.getCurrentUser().getUid();
String tskid = "CQ45RKh8Ohd6DXjSQ8RO";
DocumentReference taskref = fStore.collection("usersData").document(userId)
.collection("tasks").document(tskid);
taskref.delete().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Log.d("tag", "Task Deleted Successfully");
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("tag", "Task Deletion Unsuccessful");
}
});
}
In order to delete that document, I have used the exact same ID that exists in the database.

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 database on insert/delete document callbacks not being invoked when there is no connection

I am trying firestore database on Android.
This is my code that inserts a document:
public Observable<Post> createPost(final Post post){
return Observable.create(new Observable.OnSubscribe<Post>() {
#Override
public void call(final Subscriber<? super Post> subscriber) {
try{
DocumentReference documentReference = getCollection().document();
post.setId(documentReference.getId());
documentReference.set(post).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
subscriber.onNext(post);
subscriber.onCompleted();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
subscriber.onError(e);
subscriber.onCompleted();
}
});
}catch (Exception ex){
subscriber.onError(ex);
Log.e(LOG_TAG, ex.getMessage(), ex);
}
}
});
}
The document gets inserted into the database but neither of the onSuccess nor the onFailure callbacks are invoked.
Update 1
The issue is not consistent sometimes it works, sometimes the callbacks are invoked after an hour, sometimes after 3 hours etc..
This is happening when there is no internet connection.
Update 2
The issue was reported here and it is closed. I am not sure how to guarantee the correctness of data created offline.
There does not seem to be anything wrong with your code, try perhaps the onCompleteListener callback. So add
.addOnCompleteListener((Task<Void> task) -> {
if(task.getException() != null) {
emitter.onError(task.getException());
}
if(task.isComplete()) { //try task.isSuccessful() if this is what you are looking for
emitter.onComplete();
}
});
If this does not solve your issue perhaps, use an emitter like so:
Completable completable$ = Completable.create((CompletableEmitter emitter) -> {
firebaseFirestore.collection(collection).document(document)
.delete()
.addOnSuccessListener((Void aVoid) -> emitter.onComplete())
.addOnFailureListener((Exception e) -> emitter.onError(e))
.addOnCompleteListener((Task<Void> task) -> {
if(task.getException() != null) {
emitter.onError(task.getException());
}
if(task.isComplete()) { //try task.isSuccessful()
emitter.onComplete();
}
});
});
return completable$;
Okay so I did a simple version of your question but instead of adding a post, it adds a User. The concept is the same.
Here is the method to add a user. It returns an Observable<DocumentReference> just to reference where the user was added.
public Observable<DocumentReference> insertToFirebaseFirestore$() {
UserEntity userEntity = new UserEntity();
userEntity.setEmail("myemail#myemail.com");
userEntity.setBio("I'm a cool cat!");
userEntity.setDisplayName("KoolKat!");
//Notice here I am using an ObservableEmitter instead of Subscriber like you did
return Observable.create((ObservableEmitter<DocumentReference> emitter) -> {
this.firebaseFirestore.collection("tempUsers")
.add(userEntity)
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
emitter.onError(e);
}
})
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
#Override
public void onSuccess(DocumentReference documentReference) {
//this gets triggered when I run
emitter.onNext(documentReference);
}
})
.addOnCompleteListener(new OnCompleteListener<DocumentReference>() {
#Override
public void onComplete(#NonNull Task<DocumentReference> task) {
//this also gets triggered when I run
emitter.onNext(task.getResult());
}
});
});
}
When I run this, and place breakpoints inside onSuccess and onComplete. Both of them are triggered and I can see the output.
I call the method from the Activity as follows.
...onCreate method
insertToFirebaseFirestore$()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io()) //observe on io thread cause I don't need it to updateUI.
.subscribe((DocumentReference val) ->{
Log.e("USERACTIVITY", "You have uploaded " + val.getId());
});
The LogcatPrints
12-13 09:47:47.942 15007-15059/com.example.debug E/USERACTIVITY: You have uploaded sFBsF4ZmwGaDdxCEKuF6
12-13 09:47:57.563 15007-15059/com.example.debug E/USERACTIVITY: You have uploaded sFBsF4ZmwGaDdxCEKuF6.
From what I have see with yours, perhaps use an emitter within
your Observable.create.
If that doesn't work try doing the firestore call without wrapping
it in an observable
If all else, might be a connection issue, since you say it happens
intermittently
I came across this with react native.
For inserts the key is to create a new document.
example:
const userRef = firebase.firestore()
.collection("users")
.doc();
userRef.set({name: someName});
This will create the document offline and sync when you come back online.
Further calls such as this will work offline
userRef.collection("Locations").add({location: "Austin,TX"});

Categories

Resources