How to retrieve data from merge tasks in firestore query? - java

please can someone tell me how to retrieve all three tasks? Actually, I'm able to retrieve only two tasks.
CollectionReference players = db.collection("gamers");
Task task1 = players.whereEqualTo("player_id_one", myId)
.get();
Task task2 = players.whereEqualTo("player_id_two", myId)
.get();
Task task3 = players.whereEqualTo("player_id_three",myId).get();
Task<List<QuerySnapshot>> allTasks = Tasks.whenAllSuccess(task1, task2,task3);
allTasks.addOnSuccessListener(new OnSuccessListener<List<QuerySnapshot>>() {
#Override
public void onSuccess(List<QuerySnapshot> querySnapshots) {
for (QuerySnapshot queryDocumentSnapshots : querySnapshots) {
for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
Modelgame players = documentSnapshot.toObject(Modelgame.class);
result.add(modelplayer);
adapter.notifyDataSetChanged();
}
}
}
});

When you're using Tasks#whenAllSuccess(Task...<?> tasks) method it:
Returns a Task with a list of Task results that completes successfully when all of the specified Tasks complete successfully.
This means that the List you're getting is not a List<QuerySnapshot> but a List<Object>:
allTasks.addOnSuccessListener(new OnSuccessListener<List<Object>>() {
#Override
public void onSuccess(List<Object> querySnapshots) {
//Loop through the list only once
for (Object object : querySnapshots) {
Modelgame players = ((DocumentSnapshot) object).toObject(Modelgame.class);
result.add(modelplayer);
Log.d("TAG", players.getName());
}
adapter.notifyDataSetChanged(); //Added when the for loop ends.
}
});
So once all the tasks are successful you only need to loop through the results once and cast the object to a DocumentSnapshot object and call toObject().

Related

How to wait for nested Task in Android Firestore?

I have the following method:
public static void getRecipe(DocumentReference DR){
Recipe r = new Recipe();
/*First Firestore Query! - Get the recipe*/
DR.get().addOnCompleteListener(task -> {
if (task.isSuccessful() && task.getResult() != null) {
r.addStuff(...);
r.addMoreStuff(...);
r.addEvenMoreStuff(...);
/*Second Firestore Query(s)! - Get the ingredient(s)*/
for(DocumentReference DR_ingredient : ingredients) {
Task<DocumentSnapshot> IngredientQuery = DR_ingredient.get();
IngredientQuery.addOnCompleteListener(t -> {
if (t.isSuccessful() && t.getResult() != null) {
/*even more stuff*/
}
});
}
//After all tasks are finished, we can finally put the recipe in our Database!
Task<Void> t = Tasks.whenAll(IngredientQueries);
t.addOnSuccessListener(unused -> {
addRecipe(r);
});
}
});
}
The method uses Firestore to get a saved recipe and furthermore load the ingredients for the selected recipe. The ingredients are saved as a reference inside of the recipe and must be loaded again per Firebase.
My Question now is: If I call getRecipe(DocumentReference DR) from another class, how can I wait until everything is loaded and then continue with my code?

How to get last visible DocumentSnapshot in a combined task for Firestore queries?

I am combining two queries to do the "not equals" query for a Firestore collection which isn't possible natively in Firestore so I have to do it locally. I need to get the last visible document of my combined task to use for pagination.
Following is the code that I am writing to get a list of documents from both query results.
Query first = firebaseFirestore
.collection("jobs")
.whereGreaterThan("createdBy",currentUser.getEmail())
.orderBy("createdDate",Query.Direction.ASCENDING)
.limit(2);
Query second = firebaseFirestore
.collection("jobs")
.whereLessThan("createdBy",currentUser.getEmail())
.orderBy("createdDate",Query.Direction.ASCENDING)
.limit(2);
Task<QuerySnapshot> firstTask = first.get();
Task<QuerySnapshot> secondTask = second.get();
final Task<List<QuerySnapshot>> combinedTask = Tasks.whenAllSuccess(firstTask, secondTask);
combinedTask.addOnSuccessListener(new OnSuccessListener<List<QuerySnapshot>>() {
#Override
public void onSuccess(List<QuerySnapshot> querySnapshots) {
List<Job> list = new ArrayList<>();
for(QuerySnapshot qs: querySnapshots){
for(DocumentSnapshot document: qs){
Job job = document.toObject(Job.class);
list.add(job);
}
}
JobAdapter jobAdapter = new JobAdapter(list);
recyclerView.setAdapter(jobAdapter);
DocumentSnapshot lastVisible = combinedTask.getResult().get()
}
});
In the line
DocumentSnapshot lastVisible = combinedTask.getResult().get()
I can only either access the documents from my first query or my second query so I am unsure how to get the last visible document, I think the last document would be from my second query logically but I am unsure.
Here is the tutorial I was following to accomplish this:
https://www.youtube.com/watch?v=KdgKvLll07s
You can solve this, by simply creating the lastVisible as a global variable in your class:
private DocumentSnapshot lastVisible;
And then assignin a value in the for loop:
final Task<List<QuerySnapshot>> combinedTask = Tasks.whenAllSuccess(firstTask, secondTask);
combinedTask.addOnSuccessListener(new OnSuccessListener<List<QuerySnapshot>>() {
#Override
public void onSuccess(List<QuerySnapshot> querySnapshots) {
List<Job> list = new ArrayList<>();
for(QuerySnapshot qs: querySnapshots){
for(DocumentSnapshot document : qs){
Job job = document.toObject(Job.class);
list.add(job);
lastVisible = qs.getDocuments().get(qs.size() - 1);
}
}
Query nextQuery = firebaseFirestore
.collection("jobs")
.whereGreaterThan("createdBy",firebaseAuth.getCurrentUser().getEmail())
.orderBy("createdDate",Query.Direction.ASCENDING)
.startAfter(lastVisible)
.limit(2);
//Use the nextQuery
}
});
See the call to .startAfter(lastVisible)? So this will work because at the end of the for loop, the lastVisible object will always hold the value of the last DocumentSnapshot object.
P.S. Thanks for using my tutorial as an example for your Firestore pagination :)

fetching data from firebase and returning it to the main thread [duplicate]

This question already has answers here:
How to return a DocumentSnapShot as a result of a method?
(2 answers)
Closed 3 years ago.
Currently Running into an issue where I'm fetching data from firebase. I know it is because Firebase is asynchronous, so when I make my firebase call, it is executed on its own thread, and the current thread that it was called on continues to execute. I'm populating a list of objects with the data from firebase and I return that list of objects. The thing is, the list is always returning null because the execution of the firebase code isn't completed in time.
I created some asynchronous code that fetches from SQLite db that works fine, but this approach does not seem to work with firebase (I believe its due to firebases API being asynchronous) Here is my method to return a list of objects from firebase.
/** Method to get activity data from firebase.
* #param userInput the user query to select the data
* #return a list of activity models based on the query
* */
public List<ActivityModel> retrieveActivityData(String userInput) {
Log.d(TAG, "retrieveActivityData: starts");
List<ActivityModel> models = new ArrayList<ActivityModel>();
// execute the query in firebase
CollectionReference activfitCollection = db.collection("activity");
activfitCollection.orderBy("isoTimestamp")
.startAt(userInput)
.endAt(DateHelper.getDayEndingDate(userInput))
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Log.d(TAG, "onComplete: Getting data successful!");
// check to see if it exists
if (!task.getResult().isEmpty()) {
for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
Log.d(TAG, "retrieveActivityData: document = " + documentSnapshot.getId());
// cast the document to the activity model
Log.d(TAG, "retrieveActivityData: document data " + documentSnapshot.getData());
ActivityModel model = mapToActivityModel(documentSnapshot);
models.add(model);
Log.d(TAG, "retrieveActivityData: array size" + models.size());
}
}
} else {
Log.e(TAG, "onComplete: Error getting documents: ", task.getException());
}
});
Log.d(TAG, "retrieveActivityData: array size outside " + models.size());
return models;
}
Option - 1: You can use LiveData to achieve this. Post value to LiveData when operation complete and observe that inside your activity or fragment
MutableLiveData<List<ActivityModel>> listMutableLiveData = new MutableLiveData<>();
public MutableLiveData<List<ActivityModel>> retrieveActivityData(String userInput) {
List<ActivityModel> models = new ArrayList<ActivityModel>();
....
if (task.isSuccessful()) {
for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
....
models.add(model);
}
//Post value to live data from here
listMutableLiveData.postValue(models);
}
....
return listMutableLiveData;
}
And then observe like this
retrieveActivityData(userInput).observe(this, new Observer<List<ActivityModel>>() {
#Override
public void onChanged(List<ActivityModel> activityModels) {
//you can use list here
}
});
Option - 2: You can use callback function to get result when firebase operation complete.
Create an interface for callback
interface FirebaseResultListener {
void onComplete(List<ActivityModel> activityModels);
}
Configure your retrieveActivityData to handle this callback
public void retrieveActivityData(String userInput, FirebaseResultListener callback) {
List<ActivityModel> models = new ArrayList<ActivityModel>();
....
if (task.isSuccessful()) {
for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
....
models.add(model);
}
//Invoke callback with result from here
callback.onComplete(models);
}
....
}
Implement this interface in your activity or fragment
retrieveActivityData(userInput, new FirebaseResultListener() {
#Override
public void onComplete(List<ActivityModel> activityModels) {
//you can use list here
}
});
Since the data is loaded async you cannot return the data, you should pass callback(Interface) in retrieveActivityData method, and use callback of interface to load the data, check the code bellow
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
MyFirebaseCallback myFirebaseCallback = new MyFirebaseCallback() {
#Override
public void dataLoaded(List<ActivityModel> activityModels) {
//set the data in recycler view
}
};
retrieveActivityData("myInput",myFirebaseCallback);
}
public void retrieveActivityData(String userInput, final MyFirebaseCallback callback) {
Log.d(TAG, "retrieveActivityData: starts");
// execute the query in firebase
CollectionReference activfitCollection = db.collection("activity");
activfitCollection.orderBy("isoTimestamp")
.startAt(userInput)
.endAt(DateHelper.getDayEndingDate(userInput))
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
List<ActivityModel> models = new ArrayList<ActivityModel>();
Log.d(TAG, "onComplete: Getting data successful!");
// check to see if it exists
if (!task.getResult().isEmpty()) {
for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
Log.d(TAG, "retrieveActivityData: document = " + documentSnapshot.getId());
// cast the document to the activity model
Log.d(TAG, "retrieveActivityData: document data " + documentSnapshot.getData());
ActivityModel model = mapToActivityModel(documentSnapshot);
models.add(model);
Log.d(TAG, "retrieveActivityData: array size" + models.size());
}
}
callback.dataLoaded(models);
} else {
Log.e(TAG, "onComplete: Error getting documents: ", task.getException());
}
});
Log.d(TAG, "retrieveActivityData: array size outside " + models.size());
}
interface MyFirebaseCallback{
void dataLoaded(List<ActivityModel> activityModels);
}
You have guessed it right! The query is asynchronous so retrieveActivityData() shouldn't return List<ActivityModel> or it would always be null. You would have to use an Event Bus to fire an event as soon as your List is compiled inside onComplete() or use LiveData and observe it.
LiveData
ViewModel
EventBus

Wait until Firestore data is retrieved

I need after inner foreach finish and add all data in list then send to interface
but when but this line " view.setOrders(orderList); " below " orderList.add(order); "
my code run okye but not that is not performance , I need best way to collection list then send to interface ..
public ListenerRegistration getOrders() {
view.showLoading();
ListenerRegistration listenerRegistration = refOrders.addSnapshotListener((queryDocumentSnapshots, e) -> {
view.hideLoading();
if (e != null) {
view.onErrorMessage(e.getMessage());
} else {
List<Order> orderList = new ArrayList<>();
for (QueryDocumentSnapshot snapshot : queryDocumentSnapshots) {
Order order = snapshot.toObject(Order.class);
order.setOrderId(snapshot.getId());
refUsers.document(order.getPhone()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
User user = task.getResult().toObject(User.class);
order.setName(user.getName());
order.setAddress(user.getAddress());
orderList.add(order);
}
}
});
}
//Here Back List size = 0
view.setOrders(orderList);
}
});
return listenerRegistration;
}
Since get() method is asynchronous which means that the code after the CompletionListener will be executed first and then after the data is retrieved the listener will get executed.
Therefore to solve the issue add the following line view.setOrders(orderList); inside the CompletionListener
if (task.isSuccessful()) {
User user = task.getResult().toObject(User.class);
order.setName(user.getName());
order.setAddress(user.getAddress());
orderList.add(order);
view.setOrders(orderList);

Firestore not querying second collection

I'm kind of new to android studio and firestore database and
I'm having some trouble with querying my second firestore collection. As the title says, i am querying two collections, first one is:
with the code :
firestore = FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.build();
firestore.setFirestoreSettings(settings);
firestore.collection("Obiective").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
//<--------- Check if firestore entry is already downloaded into file --------->
SingletonObjectivesId.getInstance().getIds().clear();
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, task.getResult().size() + " number of documents");
SingletonObjectivesId.getInstance().setSize(task.getResult().size());
if(document.exists() && document != null) { ...
and the second collection have the following format:
with the code:
firestore.collection("Routes")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
Log.d(TAG, task.getResult().size() + " = task.getResult().size()");
for (QueryDocumentSnapshot document : task.getResult()) {
objectives_id.clear();
id_route = document.getId();
if(document.exists() && document != null) {
Map<String, Object> map = document.getData();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String field_name = entry.getKey() + "";
String id = document.getString(field_name) + "";
objectives_id.add(id);
}
}
routes.add(new Route(objectives, objectives_id, id_route));
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
As you can see in the second code i added a Log.d ( after if (task.isSuccessful()) ) who will display the number of documents. In my case, the first query Log.d returns 3 and the second returns 0 despite the fact that i have 2 documents in there. How can i access this 2 documents ?
Thank you.
Firebase APIs are asynchronous, meaning that the onComplete() method returns immediately after it's invoked, and the callback from the Task it returns, will be called some time later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available. Because that method returns immediately, the number of documents that you try to log, is not populated from the callback yet.
Basically, you're trying to use a value synchronously from an API that's asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended.
A quick solve for this problem would be to move the code that queries the second collection inside the first callback (inside the onComplete() method) so-called nested queries, otherwise I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
After i followed the steps from the video, i updated the code like this:
I have a global variable firestore created at the beginning of my class
private FirebaseFirestore firestore;
I have two methods readDataObjective and readDataRoute and two interfaces FirestoreCallback and FirestoreCallbackRoutes
readDataRoutes
private void readDataRoute(FirestoreCallbackRoute firestoreCallbackRoute){
firestore.collection("Trasee").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) { ...
readDataObjective
private void readDataObjective(FirestoreCallback firestoreCallback){
firestore.collection("Obiective").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
SingletonObjectivesId.getInstance().getIds().clear();
for (QueryDocumentSnapshot document : task.getResult()) { ...
Interfaces
private interface FirestoreCallback{
void onCallback(ArrayList<Objective> list);
}
private interface FirestoreCallbackRoute{
void onCallback(ArrayList<Route> list);
}
And in onCreate method i call readDataObjective and readDataRoute like this
firestore = FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder().build();
firestore.setFirestoreSettings(settings);
readDataObjective(new FirestoreCallback() {
#Override
public void onCallback(ArrayList<Objective> list) {
for(Objective item : list){
//Create plainText Object - delimiter "/~/"
String data = "Title:" + item.getTitle() + "/~/" +
............................
} else if(str.contains("Longitude:")){
obj.setLongitude(str.substring(10,str.length()));
}
start = crt + 2;
}
}
SingletonObjectivesArray.getInstance().getObjectives().add(obj);
}
readDataRoute(new FirestoreCallbackRoute() {
#Override
public void onCallback(ArrayList<Route> list) {
Log.d(TAG, 2 + " ");
ArrayList<Objective> routeObjectives = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
routeObjectives.clear();
for (int j = 0; j < SingletonObjectivesArray.getInstance().getObjectives().size(); j++){ ...
With the mention that readDataRoute is called inside readDataObjective, at the end of it.
I noticed that the problem is not only with the second query, but with the first one too. I added a new document into the first collection and after running the code, the first query return the old data ( without my new entry ).

Categories