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
}
}
Related
I have a Map of references which I read from Firestore. these refs lead me to documents that I'm willing to use their data to create an instance of my class 'Contact'.
In order to do that I've created a list of tasks which every task of it uses its ref to read from Firestore and retrieve the needed data.
Once it's all done I use Tasks.whenAll(tasks).addOnSuccessListener() willing to retrive my new array of Contacts.
On this method, 'contacts' is empty and 'data' is full of document references.
I expected Tasks.whenAll(tasks) to being called only when all this reading using the refs has completed, however it's being called immediately, therefore - nothing happens.
private void createContactArray(final ArrayList<Contact> contacts, final Map<String, DocumentReference> data) {
List<Task<DocumentSnapshot>> tasks = new ArrayList<>();
for (final Map.Entry<String, DocumentReference> entry : data.entrySet()) {
tasks.add(db.document(entry.getValue().getPath()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Map<String,String> contactDetails = (Map<String, String>) document.getData().get(entry.getKey());
Contact contact = createContact(contactDetails);
if(contact != null){ contacts.add(contact);}
} else {
Log.d(ACTION_FETCH_CONTACT_LIST,"There was ref problem with " + entry.getKey());
}
}else {
Log.d(ACTION_FETCH_CONTACT_LIST, "get failed with ", task.getException());
}
}
}));
}
Tasks.whenAll(tasks).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
sendBroadcastActionContactList(contacts);
}
});
I would like Tasks.whenAll to be called once its all finished and not right away. I wish to have a proper explanation for the issue and a decent code that should do the job instead of mine.
I really appreciate your help!
You are using the APIs incorrectly. You should be collecting tasks returned by get() into an array, instead of immediately adding a callback to each one. Pass that list of tasks to Tasks.whenAll(). Then, in the callback for the task returned by Tasks.whenAll, you can examine each DocumentSnapshot results.
I have multiple buttons in my android application and when clicked it has to fetch data from Firebase database and the fetched data is added into a list which has to be passed as an argument to another class constructor . But the fetching of data happens in another thread by default since firebase queries are asynchronous . How do I make sure that list has been added with data from other thread before passing it to constructor ?
It's simple use oncompletelistener you can refer this example:
db=FirebaseFirestore.getInstance();
db.collection("Order").document(TableListFragment.tableno)
.update(
"Items", FieldValue.arrayUnion("ABCD"),
"Quantity", FieldValue.arrayUnion("34")
).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Toast.makeText(getContext(),"Item Added",Toast.LENGTH_LONG).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.i("onFailure:",e.toString());
}
});
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);
}
I am have the follow code:
public synchronized void next(final RoomListQueryResultHandler handler) {
this.setLoading(true);
roomList = new ArrayList<Room>();
this.database.child("members").child(this.mUser.getUid()).child("rooms")
.limitToFirst(this.mLimit)
.startAt(this.currentPage * this.mLimit)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
RoomListQuery.this.setLoading(false);
//mListAdapter.setLoading(false);
if (!dataSnapshot.hasChildren()) {
RoomListQuery.this.currentPage--;
}
for (DataSnapshot ds : dataSnapshot.getChildren()) {
Room room = ds.getValue(Room.class);
//roomList.add(Room.upsert(room));
Room.getRoom(room.getId(), new Room.RoomGetHandler() {
#Override
public void onResult(Room room, customException e) {
if (e != null) {
// Error!
e.printStackTrace();
return;
}
roomList.add(room);
}
});
handler.onResult(roomList, (customException) null);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
handler.onResult((List) null, new customException(databaseError.toString()));
}
});
}
}
If they are see, I have two Handlers, at first I call a list of "rooms" from Firebase, and then for each one I get the detail in other query.
The problem is that the response is a empty list, since the function not wait for all query details to be executed for the rooms, so the variable roomList always returns empty.
Any idea what I can implement, or what other methodology to use to solve it?
Thank you very much!
Greetings.
Depending on how your application is structured, you might want to change the database design so that there is no need to perform an additional Firebase query for each room retrieved from the first query.
//mListAdapter.setLoading(false);
If you're creating a list view where each row is from the /members/<user_id>/rooms Firebase node, what are the minimum room attributes necessary to display that list? If it's just a few things like room name, photo url, owner, room_id, etc you might be better off duplicating those from the original source. Then clicking one of those rows can trigger the original additional Firebase query you had as part of Room.getRoom(room.getId(), new Room.RoomGetHandler() { ... });, to navigate to a new screen / display a modal with the full room details once retrieved.
Update
To address your comment about requiring the extra data, in that case, as part of the Room class I would include an extra boolean value _loadedDetails set initially to false. So that for rendering a room within the list, when _loadedDetails is currently false just display a loading spinner. That way you can still perform those additional queries and when completed, update the appropriate Room object within roomList based on the index. Something like this:
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
RoomListQuery.this.setLoading(false);
//mListAdapter.setLoading(false);
if (!dataSnapshot.hasChildren()) {
RoomListQuery.this.currentPage--;
}
int i = 0;
for (DataSnapshot ds : dataSnapshot.getChildren()) {
Room room = ds.getValue(Room.class);
roomList.add(room); // here instead
updateRoom(room, i);
i++;
}
handler.onResult(roomList, (customException) null);
}
...
// outside of the ValueEventListener
public void updateRoom(room, index) {
Room.getRoom(room.getId(), new Room.RoomGetHandler() {
#Override
public void onResult(Room room, customException e) {
if (e != null) {
// Error!
e.printStackTrace();
return;
}
room._loadedDetails = true; // make that publicly accessible boolean, or include a setter method instead
roomList.set(index, room);
}
});
}
I have Task, that fetches results from server. While storing that data to database in bolt's .continueWith It got interrupted with something "completedImmediately" (found while debugging cycle).
this.someMethod.getStatements()
.continueWith(new Continuation<List<Statement>, Object>() {
#Override public Object then(Task<List<Statement>> task) {
for (Statement statement : task.getResult()) {
saveStatement(statement);
}
return null;
}
});
Found solution, there was a problem in inner method, which was storing data to database.