I have one Fragment that updates a field in Firestore and then after the update the next Fragment gets opened. There the updated data should be displayed in two TextViews but currently the method I used to retrieve the date from Firestore is loading the old data, not the from the previous Fragment updated data.
It seems that the new Fragment gets opened too fast so it still reads the old data from Firestore, how can I achieve that it retrieves the updated data?
I already tried to call the method in Fragment 2 in the onViewCreated and onStart method but also there it gets the old data:
Here is my first Fragment that updates the fields in Firestore before opening the next Fragment: ( I call the method below in the onClickmethod)
...
gamesRef.document(gameId).update(currentUserUid, pointsPlayer1Str)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
((GameActivity) getActivity()).setViewPager(7);
}
});
...
The I try to retrieve the updated data in the second Fragment, in the onCreateView:
...
gameRef.document(gameId).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document != null) {
pointsPlayer1Str = document.getString(currentUserUid);
pointsPlayer2Str = document.getString(uidPlayer2);
textViewPointsCurrentUser.setText(pointsPlayer1Str);
textViewPointsOpponent.setText(pointsPlayer2Str);
}
}
}
});
...
Where should I call the method from the second fragment to get the updated data?
Any help is much appreciated!
You can attach a real-time listener to your data instead with the following:
val docRef = gameRef.document(gameId)
docRef.addSnapshotListener { snapshot, e ->
if (e != null) {
Log.w(TAG, "Listen failed.", e)
return#addSnapshotListener
}
if (snapshot != null && snapshot.exists()) {
Log.d(TAG, "Current data: ${snapshot.data}")
} else {
Log.d(TAG, "Current data: null")
}
}
Related
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.
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
}
I'm trying to retrieve document references from Firestore in a for loop (without iterating through the subset). I need to wait for the loop to finish, wait for data to be received and on success, submit this data to the Firestore. Currently, my method does not wait for data to be received what so ever since it is async.
It would probably be a good idea to create a method that returns a Task and then wait for a result. Suggestions?
ArrayList<String> documentPath = new ArrayList<>();
private void getDocumentRef() {
try {
for (String path : documentPath) {
db.document(path).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful() && task.getResult() != null) {
if (task.getResult().exists()) {
references.add(task.getResult().getReference());
}
}
}
});
}
} catch (Exception e) {
}
}
If I understood your issue correctly, following method should work:
private void getDocumentRef(List<String> documentPaths) {
List<Task<DocumentSnapshot>> tasks = new ArrayList<>();
for (String path : documentPaths) {
tasks.add(db.document(path).get());
}
Task<List<DocumentSnapshot>> finalTask = Tasks.whenAllSuccess(tasks);
finalTask.addOnSuccessListener(new OnSuccessListener<List<DocumentSnapshot>>() {
#Override
public void onSuccess(List<DocumentSnapshot> documentSnapshots) {
/* This list contains all the retrieved document snapshots. Now iterate
through this list to get the document references.*/
for (DocumentSnapshot snapshot : documentSnapshots) {
references.add(snapshot.getReference());
}
/* Here u can do whatever u want with the list named references
because now it has references of all required documents. */
}
});
}
Here, we iterate through the supplied list of paths, create a separate Task for retrieving the document at that path and add this task to a list of Task<DocumentSnapshot>. Then, we supply this list to Tasks.whenAllSuccess() and create a new Task named finalTask. We then attach an OnSuccessListener to finalTask whose onSuccess() method is called when all the supplied tasks get completed. What we get in onSuccess() is a list of DocumentSnapshot of each document. We can now go through this list and get the DocumentReference.
Hope it helps!
For continuing after completing your requests for the selected subset, you would need to keep track of how many queries you're making and how many have completed.
Create a member variable to track the queries and within your OnCompleteListener:
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
mCompleted++; // Update your member variable
if(task.isSuccessful(){
// Do something with your returned data
}else{
// The task failed
}
// Check if the last query has completed
if(mCompleted == numOfQueries){
mCompleted = 0; // Reset the completed queries if you might run this process again
// All of your queries have returned and you can now do something with the complete data set
}
}
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);
}
Problem: My list items display in the wrong order. This happens when I close the fragment and re-open it. It then displays all the "sent messages" first, and then the received messages after. However, when I'm in writing the messages, they appear in the correct order. It's only when I close the fragment/activity and re-open it that the order has changed.
I call the getMessages method in my on-create method for opening the fragment containing the view.
What I've tried:
Using the Firestore orderby method (both with String and TimeStamp)
Using the simpler Firestore Snapshot Listener
Question:
How do I best use the Firestore Snapshot Listener with a RecyclerView and maintain the order of the items correctly?
Here is my main "getMessages" method:
public void getLiveChatMessages(final ArrayList<ChatConversationMessage> messageArrayList, final ChatConversationAdapter adapter, final String matchClicked) {
final String userID = onboardingFirebaseUser.returnCurrentUserId();
final CollectionReference messagesCollectionRef = db.collection("users")
.document(userID)
.collection("matches")
.document(matchClicked)
.collection("messages");
messagesCollectionRef
.orderBy("TimeStamp", Query.Direction.DESCENDING)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "listen:error", e);
return;
}
for (QueryDocumentSnapshot doc : value) {
if (doc.get("Message") != null) {
if (doc.get("Message") != null && doc.get("From user with ID").equals(userID)) {
String message = doc.getString("Message");
messageArrayList.add(new ChatConversationMessage(message));
adapter.notifyDataSetChanged(); //Ensures messages are visible immediately
} else if (doc.get("Message") != null) {
final String message = doc.getString("Message");
DocumentReference matchRef = db.collection("users")
.document(userID)
.collection("matches")
.document(matchClicked);
matchRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
imageReference = storageReference.child(document.getString("profileImg"));
messageArrayList.add(new ChatConversationMessage(message, imageReference));
adapter.notifyDataSetChanged(); //Ensures messages are visible immediately
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
}
}
}
}
});}}
After some time I've found the problem.
I was calling a the get-method on a new reference within the snapshot listener. When you do this, it impacts the order of the items in your ArrayList.
To solve it, ensure that all the items you need from Firestore to create your ArrayList is stored in the same location as fields on each document (and not in two different locations). That way, you don't need to use a separate get-method on a new reference within a snapshot listener. This also keeps client-side code cleaner. For future Googlers, here is how I restructured my method:
messagesCollectionRef
.orderBy("TimeStamp", Query.Direction.ASCENDING)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot snapshots,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "listen:error", e);
return;
}
for (DocumentChange dc : snapshots.getDocumentChanges()) {
switch (dc.getType()) {
case ADDED:
Log.d(TAG, "New message added" + dc.getDocument().getData());
if (dc.getDocument().get("Message") != null && dc.getDocument().get("From user with ID").equals(userID)) {
String message = dc.getDocument().getString("Message");
messageArrayList.add(new ChatConversationMessage(CURRENTUSER, message, null));
adapter.notifyDataSetChanged();
}
if (dc.getDocument().get("Message") != null && dc.getDocument().get("From user with ID").equals(matchClicked)) {
String message = dc.getDocument().getString("Message");
imageReference = storageReference.child(dc.getDocument().getString("profileImg"));
messageArrayList.add(new ChatConversationMessage(OTHERUSER, message, imageReference));
adapter.notifyDataSetChanged();
}
break;
case MODIFIED:
break;
case REMOVED:
break;
}
}
}
});
As you can see, I've now stored the imageReference String within each message doc in Firestore, and it can be retrieved in the same way I retrieve all the other data I need to make an addition to my ArrayList. The major code change you need to do is where you write your data to the cloud (ie. write/set Firestore docs). That's where you'll need to make sure that everything is added as field values to your doc, so you don't need to get it in separate locations. Good luck!