How to wait for nested Task in Android Firestore? - java

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?

Related

How to retrieve data from merge tasks in firestore query?

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().

Android - Transaction - Task is not yet complete

I found a few examples of "task not yet complete," but have not found any examples for transactions. I am using a transaction because in my application I need the operation to be able to fail if there is no internet connection. I can detect this with a transaction.
I have a Collection with Documents. I am trying to obtain the names of the documents. Sometimes the code works fine, but majority of the time I get the "task not yet complete" error. The frustrating thing is that I have a callback for "onComplete" so it's weird that the transaction isn't complete when the callback is... called.
I get the "task not yet complete exception in the onCompleteListener(). What's frustrating is that I even check to ensure if (task.isSuccessful() && task.isComplete()). Do I need to use a continuation? If so, please provide an example - I just don't quite understand it yet.
// Note: states is an ArrayList<String>
// snapshot is a QuerySnapshot
public void getStatesList(){
states.clear();
states.add("Select A State");
db.runTransaction(new Transaction.Function<Void>() {
#Nullable
#Override
public Void apply(#NonNull Transaction transaction) {
// Collect Snapshot data
snapshot = db.collection("DATA").get();
return null;
}
}).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful() && task.isComplete()){
try{
for(QueryDocumentSnapshot document : snapshot.getResult()){
states.add(document.getId());
}
sendResponseToActivity("Success", RESULT_OK);
} catch (Exception e){
e.printStackTrace(); // Transaction is not yet complete
sendResponseToActivity("Fail", RESULT_OK);
}
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
if(e.getMessage().contains("UNAVAILABLE"))
sendResponseToActivity("NoInternet", RESULT_OK);
else
sendResponseToActivity("Fail", RESULT_OK);
}
});
} // End getStatesList()
In your code, you call db.collection("DATA").get() but this doesn't operate inside of the transaction (that is done using transaction.get(docRef), transaction.update(docRef, newData) and so on). Just because the Task of the "transaction" has completed, it doesn't mean that this rogue database call has.
If the purpose is to get the server's copy of /DATA and only the server's copy, use Query#get(Source source) with Source.SERVER. (Note: pending server writes may be merged into the data to reflect the most up-to-date copy of the server's data)
public void getStatesList(){
states.clear();
states.add("Select A State");
db.collection("DATA").get(Source.SERVER)
.addOnSuccessListener(querySnapshot -> {
for(QueryDocumentSnapshot document : querySnapshot){
states.add(document.getId());
}
sendResponseToActivity("Success", RESULT_OK);
})
.addOnFailureListener(ex -> {
if (ex.getMessage().contains("UNAVAILABLE")) {
sendResponseToActivity("NoInternet", RESULT_OK); // RESULT_OK?
} else {
sendResponseToActivity("Fail", RESULT_OK); // RESULT_OK?
}
});
}
However, because the above version uses and modifies global variables, I would implement it using:
/** Returns server's list of states */
public Task<ArrayList<String>> getStatesList() {
return db.collection("DATA").get(Source.SERVER)
.onSuccessTask(querySnapshot -> {
ArrayList<String> states = new ArrayList<>();
// NOTE: states.add("Select A State"); was removed here
for (QueryDocumentSnapshot document : querySnapshot) {
states.add(document.getId());
}
return Tasks.forResult(states);
});
}
Then use it like so:
getStatesList().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
ArrayList<String> states = task.getResult();
// do something with states, like update UI
// don't forget that "Select A State" isn't in the array
} else {
Exception ex = task.getException();
if (ex.getMessage().contains("UNAVAILABLE")) {
// offline, do something
} else {
// unexpected error, do something
}
}
})

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);

RxJava Can't create handler inside thread that has not called Looper.prepare()

Okay, so I am having a bit of trouble with an RxJava Observable I am using in my Android app. It is extremely frustrating because this has been working for a while now and is only now throwing the error above. I am aware that this means I am doing a UI operation from another thread but I do not see where that is happening. So here is the observable:
ConnectionsList.getInstance(this).getConnectionsFromParse(mCurrentUser)
.delay(3, TimeUnit.SECONDS)
.flatMap(s -> mainData.getDataFromNetwork(this, mCurrentUser, mSimpleFacebook))
.flatMap(s -> mainData.getPictureFromUrl(s))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Bitmap>() {
#Override
public void onCompleted() {
if (mCurrentUser.get(Constants.NAME) != null) {
mNavNameField.setText((String) mCurrentUser.get(Constants.NAME));
}
mSearchFragment.setupActivity();
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
mSearchFragment.setEmptyView();
}
#Override
public void onNext(Bitmap bitmap) {
mNavProfImageField.setImageBitmap(bitmap);
mainData.saveImageToParse(bitmap); //save the image to Parse backend
}
});
After some debugging I found that this flatmap is where the error is happening:
.flatMap(s -> mainData.getDataFromNetwork(this, mCurrentUser, mSimpleFacebook))
Inside of that is the following, I have added a comment where the error is being thrown:
return Observable.create(subscriber -> {
//set the username field if ParseUser is not null
if(currentUser != null) {
username = (String) currentUser.get(Constants.NAME);
}
//if prof pic is null then request from facebook. Should only be on the first login
if (currentUser != null && currentUser.getParseFile(Constants.PROFILE_IMAGE) == null) {
if (AccessToken.getCurrentAccessToken() != null) { //check if session opened properly
//get simple facebook and add the user properties we are looking to retrieve
Profile.Properties properties = new Profile.Properties.Builder()
.add(Profile.Properties.FIRST_NAME)
.add(Profile.Properties.GENDER)
.add(Profile.Properties.BIRTHDAY)
.add(Profile.Properties.ID)
.add(Profile.Properties.EMAIL)
.build();
//The following line is where the debugger stops
//and the error gets thrown
simpleFacebook.getProfile(properties, new OnProfileListener() {
#Override
public void onComplete(Profile response) {
String id = response.getId();
String name = response.getFirstName();
String gender = response.getGender();
String birthday = response.getBirthday();
String email = response.getEmail();
String age = getAge(birthday);
currentUser.put(Constants.NAME, name);
currentUser.put(Constants.AGE, age);
currentUser.put(Constants.GENDER, gender);
currentUser.put(Constants.EMAIL, email);
currentUser.saveInBackground();
if (id != null) { //display the profile image from facebook
subscriber.onNext("https://graph.facebook.com/" + id + "/picture?type=large");
}
}
Whats going on here? Its been working fine as is and now it is saying that I am on some helper thread. As far as I was concerned I was on the UI thread up to this point. If someone can help me that would be great, otherwise I may have to drop RxJava for the time being as it is not working out for me. Thanks in advance!
It looks like the simpleFacebook.getProfile(properties, listener) call does it's own threading internally. Typically you don't want this with rxjava. You want rxjava to take care of the threading and all code you write should be synchronous.
I am not familiar with the Facebook SDK, but I would look for a call that executes the request synchronously. So a method that looks like public Profile getProfile(Properties properties) { instead of one returning void and requiring a listener.
Hope this helps..

Categories