Performing Callbacks one after another - java

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.

Related

Android Firebase Firestore query returns empty results from function, but the same code works when included directly

I'm having a trouble with firebase queries in Studio.
I'm trying to abstract a simple query to get a list of object stored in a collection (in my case "users")
I want to create a function stored in a Class that can be called by every fragment into the project.
But I don' t find any method to do that, is to repeate the same instruction the only way to do that?
Here is an example
db.collection("users") //get all the users
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
if(task.getResult() != null) {
for (QueryDocumentSnapshot document : task.getResult()) {
usersList.add(document.toObject(User.class));
} else {
Log.w(LOGIN, "Error getting documents.", task.getException());
}
});
I write these lines of code every time I need them, but I want to create a method that return a List as in this example:
public static List<User> getUsers(FirebaseFirestore db) {
List<User> usersList = new ArrayList<>();
db.collection("users") //get all the users
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
if(task.getResult() != null) {
for (QueryDocumentSnapshot document : task.getResult()) {
usersList.add(document.toObject(User.class));
} else {
Log.w(LOGIN, "Error getting documents.", task.getException());
}
});
} else {
//error
}
return usersList;
}
Data is loaded from Firestore (and most modern cloud APIs) asynchronously, because it may take some time. Instead of blocking the app during that time, the main code continues to execute. Then when the data is available, your addOnCompleteListener callback is executed with that data.
The easiest way to see this is by adding some well-placed logging to your code:
public static List<User> getUsers(FirebaseFirestore db) {
Log.i(LOGIN, "Starting getUsers");
db.collection("users") //get all the users
.get()
.addOnCompleteListener(task -> {
Log.i(LOGIN, "Got data");
})
Log.i(LOGIN, "Returning from getUsers");
}
When you run this code, you get the following output:
Starting getUsers
Returning from getUsers
Got data
This is probably not the order you expected, but it completely explains why the code that calls getUsers never sees the data: by the time your return usersList runs, the data hasn't loaded yet and usersList.add(document.toObject(User.class)) has never been called.
The solution is always the same: any code that needs the data from the database, must either be inside the completion callback, be called from there, or be synchronized by some other means.
A simple example is to create a custom callback function:
public interface GetUsersCallback {
void onCallback(List<User> users);
}
You then pass that to getUsers, which can then call it once it's gotten and processed the results from the database:
public static void getUsers(FirebaseFirestore db, GetUsersCallback callback) {
// 👆
List<User> usersList = new ArrayList<>();
db.collection("users") //get all the users
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
if(task.getResult() != null) {
for (QueryDocumentSnapshot document : task.getResult()) {
usersList.add(document.toObject(User.class));
} else {
Log.w(LOGIN, "Error getting documents.", task.getException());
}
});
callback(usersList); // 👈
}
}
And you can then use it like this:
getUsers(new GetUsersCallback() {
#Override
public void onCallback(List<User> users) {
Log.i(LOGIN, "Found "+users.size()+" users");
}
});
Asynchronous loading of data is incredibly common when dealing with cloud APIs, but it's also quite confusing when you first encounter it. I recommend reading some of these answers to learn more about it:
How to check a certain data already exists in firestore or not
How to return a DocumentSnapShot as a result of a method?
Why does my function that calls an API return an empty or null value?
Firebase Firestore get data from collection

Why does the command transaction.update is executed before the carrelloAttuale.prodotti.add() command

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.

How chain queries in firestore and wait retrieve data

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.

How to perform chain tasks in RxJava?

I want to perform tasks in RxJava one by one.
For Example:-
1. Fetch User Ids from Server
2. Fetch Users from server by thier Ids.
I have tried this method
public Observable<List> getUids(){
return Observable.create(emitter -> {
List<String> uids = new ArrayList<>();
//fetchData from server
emitter.onNext(uids);
});
}
public Observable<User> getUser(String uid){
return Observable.create(emitter -> {
User user = new User();
//fetchData user from server
emitter.onNext(user);
});
}
//Executing this code like
getUids().flatMapIterable(ids -> ids)
.flatMap(this::getUser)
.subscribe(new Observer<User>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(User user) {
print("next "+user.getName());
}
#Override
public void onError(Throwable e) {
print("error "+e.getMessage());
}
#Override
public void onComplete() {
print("complete");
}
});
There are some problems in it
1.this is not calling Subscriber's onComplete() method when all users are fetched.
2.if there is an error in getUser method, app is crashing. with io.reactivex.exceptions.UndeliverableException exception
Can you please tell me where I am mistaking?
Call emitter.onComplete() in your getUids() and getUser(...) Observables and then append .toList() after .flatMap(this::getUser).
Returns a Single that emits a single item, a list composed of all the items emitted by the finite source ObservableSource.
UndeliverableException is a wrapper for the exception that is happening in your .flatMap(this::getUser). I can not help you more with the information that you provided, what is it that you want to happen when an exception is thrown?

Read Data from Cloud FireStore Android

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.

Categories

Resources