I would like to chain query in firestore. I need some informations about one collections before get other information in an other collection.
I have already try to use Tasks.whenall()... but doesn't efficient.
I try to use callBack too.
Here my first function :
public static void getAllFavoris(){
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
FirebaseFirestore.getInstance().collection("product").document("favoris").collection(uid).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {//task is succesful
Log.e("TAG","task succes for fav ");
for (QueryDocumentSnapshot document : task.getResult()){//never enter in this loop
Log.e("TAG","Doc "+document);
Log.e("TAG", "Succes for get all favoris");
Log.e("TAG","data for favoris ::: "+document.getId());
MainActivity.favorisList.add(document.getId());
}
}
else {
Log.d("TAG", "Error getting documents: ", task.getException());
}
//call without data retrieve
Log.e("TAG","favoris ::: "+showListContentS(MainActivity.favorisList));
getProductByTagFound();
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e("TAG","error get All favoris"+e);
}
});
}
And here the second query i need :
public static void getProductByTagFound(){
for(int i=0;i<MainActivity.allTags.size();i++){ //allTags is not empty and i need to finish this loop
String tagId = MainActivity.allTags.get(i).toString();
FirebaseFirestore.getInstance().collection("product").document("allProduct").collection("productByTag").document(tagId).get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
Log.e("TAG", "Succes for get productByTag");
Product pdt = task.getResult().toObject(Product.class);
MainActivity.productByTag.add(pdt);
}
}
});
}
//this must be call after the loop is finish but call in the same time.
Log.e("TAG","Get product BY Tag"+showListContentP(MainActivity.productByTag));
createFinalList();
}
I need that to call createFinalList() after the loop finish and also to enter in the loop for favoris get data and call getProductByTag() after.
If you want to perform a new query right after the first one is finished, you need to wait untill the first query completes. To sovle this, you need to use nested queries. With other words, you need to move the second query right inside the first callback, inside onComplete() method. In this way, the second query will be performed only when the first one completes.
Related
I have some Firestore requests that I try to get in a for loop, but because Firebase queries are running Async, the results return in random order. Do you have any way to fix it? My code is below.
Thank you in advance!
for(Feed feed: feedList){
tasks.add(db.document(feed.getMarker().getPath()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
myMarker.add(task.getResult().toObject(SavedMarker.class));
System.out.println("Marker: "+ Objects.requireNonNull(task.getResult().toObject(SavedMarker.class)).getDescription());
System.out.println("Marker: "+task.getResult().getId());
}
}));
tasks.add(db.document(feed.getUser().getPath()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
myUser.add(task.getResult().toObject(Users.class));
}
}));
}
Tasks.whenAllSuccess(tasks).addOnCompleteListener(new OnCompleteListener<List<Object>>() {
#Override
public void onComplete(#NonNull Task<List<Object>> task) {
//Do Stuff
}
For Example:
IDX Gives ResultX
In my feedList I have saved 4 ids like:
ID1
ID2
ID3
ID4
But when i try to receive their results with the use of a loop i get:
Result2
Result1
Result3
Result4
The order is usually random.
because Firebase queries are running Async, the results return in random order.
The whenAllSuccess() method from the Tasks class will always provide the documents from the tasks right into the callback in a List<Object>. The order is the same as the order in which the tasks were added to the whenAllSuccess() method. However, if you need an order other than that, then you should either order them on the client in the way you want or create a query based on a field and order the documents as needed.
I am new to doing asynchronous programming in Android Java. I am wondering if there is a way to run another Callback after an initial Callback function has completed. Right now, I think they are running in parallel even though the second relies on the first.
First Callback:
// GETTING USER
private interface FirestoreUserCallback {
void onCallback (User myUser);
}
private void getUser(final FirestoreUserCallback firestoreCallback) {
Task<DocumentSnapshot> task = fStore.collection("users").document(fAuth.getCurrentUser().getUid()).get();
task.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
user = documentSnapshot.toObject(User.class);
firestoreCallback.onCallback(user);
Log.d(TAG, "user created");
}
});
task.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "user creation failed");
}
});
}
Second Callback:
// GETTING ALL DOCUMENTS
private interface FirestoreDocumentCallback {
void onCallback (List<TableEntries> myEntries);
}
private void getDocuments (final FirestoreDocumentCallback firestoreDocumentCallback) {
fStore.collection("result")
.document(Integer.toString(user.getCompanyNumber())) // need to use User object returned from the first Callback
.collection("SAM").get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
List<TableEntries> results = new ArrayList<>();
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
// add objects to results ArrayList ...
Log.d(TAG, document.getId() + " => " + document.getData());
}
firestoreDocumentCallback.onCallback(results);
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
onCreate:
getUser(new FirestoreUserCallback () {
#Override
public void onCallback(User myUser) {
user = myUser;
}
});
getDocuments(new FirestoreDocumentCallback() {
#Override
public void onCallback(List<TableEntries> myEntries) {
entries = myEntries;
}
});
getDocuments() relies on the user variable being given its value from the first Callback. I'm receiving this error:
java.lang.NullPointerException: Attempt to invoke virtual method 'double java.lang.Double.doubleValue()' on a null object reference
Callbacks are looking fine. You just need to check if your value is null or not before accessing it. Just add a null check
if(doubleValue!=null)
Using RxJava. First, we fetch the user and then fetch the documents. Rx-Java has an operator flatmap. flatmap is used to execute the sequential tasks, where the second task is dependent on the data from the first task.
final CompositeDisposable disposable = new CompositeDisposable();
//function to fetch user data
Single<User> getUser(){
return API.getUserData(...);
}
//function to fetch ducuments
Sinlge<UserDetail> getDocuments(int userId){
return API.getUserDetail(userId, ...);
}
//Subscribe
disposable.add(getUser()
.flatmap(user-> return getDocuments(...))
.subscribeOn(Scheduler.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObservable(){
#Override
public void onSuccess(UserDetail userDetail){
Log.v("Api result", "Successful";
//Do some work
}
#Override
public void onError(Throwable e)
Log.v("Api result", "Error Returned");
}
}));
If either of the API call fails, onError() is called. If first API fails, second API call is not executed and onError() is called.
The simplest solution for your use-case is to pass both queries to Tasks.whenAllSuccess() method, as explained in my answer from the following post:
Firestore - Merging two queries locally
So once the task is complete, you can use the elements from both queries. Another solution might be to use Android Jetpack with LiveData along with ViewModel, as the Android team recommends.
How should I fetch the document fields from one collection and combine them to add a new document to another collection? I have attached picture of the database how does it looks, I want to fetch the fields from the collection show and want to update it to the new collection along with some other data:
private void savePost(String mPostTitle, String mPostContent, String mlistSpinnerC) {
final DocumentReference docRef = FirebaseFirestore.getInstance().collection("users").document(mauth.getCurrentUser().getUid());
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document != null) {
String username = (String)
document.get("username");
String email= (String) document.get(email);
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
postMap.put(Constants.POSTTTITLE, mPostTitle);
postMap.put(Constants.POSTCATEGORY, mlistSpinnerC);
postMap.put(Constants.POSTCONTENT, mPostContent);
postMap.put(Constants.TIMESTAMP, (System.currentTimeMillis()/1000));
postMap.put(Constants.USER_ID,mauth.getCurrentUser().getUid());
postMap.put("username", username);
PostsRef.document().set(postMap).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
Intent toHomeActivity = new Intent(AddPostActivity.this, MainActivity.class);
startActivity(toHomeActivity);
}
}
});
I am just not able to map the fields from one collection to another collection, please guide me the correct method to that.
By the time you are trying to add the username to your postMap using the following line of code:
postMap.put("username", username);
The data has not finished loading yet from the database and this is because the listener you have added to your get() call is being invoked some unknown amount of time later after your query finishes. You don't know how long it's going to take, it may take from a few hundred milliseconds to a few seconds before that data is available. The onComplete() method has an asynchronous behavior, that's why you cannot get that username in such a way.
A quick solve for this problem would be to move all that block of code related to adding data to the postMap, inside the onComplete() method. In this you are waiting for the callback and username your will be available. 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.
I'm trying to get a product from a document form the cloud firestore and then put that product in the shopping cart. When i read (successfully) the product, i try to put it in an arraylist that is declared outside but it doesnt work unless i put final to the variable.
Doing so, when I run the code below, I successfully retrieve the data, but the operation carrelloAttuale.prodotti.add(prod) is executed after the command transaction.update(), so the update doesn't upload nothing different from the start.
//prendo l'utente
FirebaseAuth auth= FirebaseAuth.getInstance();
//mi salvo il codice del prodotto scannerizzato
final String codiceProdottoScannerizzato=String.valueOf(intentData);
final FirebaseFirestore db = FirebaseFirestore.getInstance();
final DocumentReference docRef = db.collection("carrelli").document(auth.getUid());
final DocumentReference docrefprodotti = db.collection("prodotti").document(codiceProdottoScannerizzato);
db.runTransaction(new Transaction.Function<Void>() {
#Override
public Void apply(Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot snapshot = transaction.get(docRef);
final Carrello carrelloAttuale = snapshot.toObject(Carrello.class);
docrefprodotti.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Prodotti prod=document.toObject(Prodotti.class);
prod.id=codiceProdottoScannerizzato;
prod.totalePezziCarrello=1;
carrelloAttuale.prodotti.add(prod);
Log.d(TAG, "PRODOTTO: " + prod.toString());
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
Log.d(TAG, "CARRELLO FB: " + carrelloAttuale.size());
transaction.update(docRef, "prodotti", carrelloAttuale.getProdotti());
// Success
return null;
}
}).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Transaction success!");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Transaction failure.", e);
}
});
I expect that the command update is executed after the carrelloAttuale.prodotti.add(prod)
in the debug log the order of tags are:
CARRELLO FB: 0
PRODOTTO: Nome: latte
Data is loaded from Firestore asynchronously, since it may have to be retrieved from the server. To prevent blocking the app, the main code continues while the data is being retrieved. Then when the data is available, your onComplete gets called.
This means that any code that needs the data from the data, must be inside the onComplete method, or be called from there. So something like:
docrefprodotti.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Prodotti prod=document.toObject(Prodotti.class);
prod.id=codiceProdottoScannerizzato;
prod.totalePezziCarrello=1;
carrelloAttuale.prodotti.add(prod);
Log.d(TAG, "PRODOTTO: " + prod.toString());
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
Log.d(TAG, "CARRELLO FB: " + carrelloAttuale.size());
transaction.update(docRef, "prodotti", carrelloAttuale.getProdotti());
}
});
Also see:
How to return a DocumentSnapShot as a result of a method?
Firebase Firestore get data from collection
"the command update" is executed before "carrelloAttuale.prodotti.add(prod)" is called because the onComplete() method has an asynchronous behaviour and returns immediately. This means that listener will not get invoked until some time later, after the database update operation is complete. There is no guarantee how long it will take. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds for the update operation to complete.
If you want to use some logic with that data, you must wait until the asynchronous Firebase database operation is complete. This means that you can only use the prod object inside the listener callback itself.
For more informarions, 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.
Is this script wrong, because the data I receive is null while I've added data on the Cloud Firestore. I do not use RecyclerView because I only need one data only.
This is the script:
private void getCustomer(){
firestoreDB.collection("customer")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
customers = new ArrayList<>();
for (DocumentSnapshot doc : task.getResult()) {
Customer customer = doc.toObject(Customer.class);
customer.setId_customer(doc.getId());
customers.add(customer);
}
} else {
// Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
firestoreListener = firestoreDB.collection("customer")
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
if (e != null) {
// Log.e(TAG, "Listen failed!", e);
return;
}
customers = new ArrayList<>();
for (DocumentSnapshot doc : documentSnapshots) {
Customer customer = doc.toObject(Customer.class);
customer.setId_customer(doc.getId());
customers.add(customer);
}
}
});
id_customer = customers.get(0).getId_customer();
}
and this is my firestore:
You cannot use something now that hasn't been loaded yet. With other words, you cannot simply use the following line of code:
id_customer = customers.get(0).getId_customer();
Outside the onSuccess() method because it will always be null due the asynchronous behaviour of this method. This means that by the time you are trying to use the id_customer variable outside that method, the data hasn't finished loading yet from the database and that's why is not accessible.
A quick solve for this problem would be to use that result only inside the onSuccess() method, or if you want to use it outside, I recommend you see the last part of my anwser from this post in which I have exaplined how it can be done using a custom callback. You can also take a look at this video for a better understanding.