Problems with array in addChildEventListener (Firebase) - java

Integer scores[] = new Integer[10];
int count = 0;
I have some data that I want to put into array. I put it in onChildAdded, but if I need to get the data from the array out of the onChildAdded, console shows "null".
If I will try to get the data of array into onChildAdded, it will be
success
data.child("scores").addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
count++;
scores[count] = dataSnapshot.getValue(Integer.class);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
int i = scores[1];
IMPORTANT MOMENT
For example, If I will use operation FOR
for (int i = 0; i < 10; ++i) {
scores[i] = i;
}
int i = scores[3];
i will not be null

Firebase APIs are asynchronous, meaning that each one of those methods: onChildAdded(), onChildChanged() etc, return 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 scores array has not have been populated from the callback yet and that's why is empty.
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 following lines of code:
for (int i = 0; i < 10; ++i) {
scores[i] = i;
}
int i = scores[3];
Inside the onChildAdded() method, where the data is available.
If you need to loop your score array outside the callback, 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.

Related

Wait for Firestore queries to complete

I am currently trying to run multiple queries in firestore and want to wait for them to all complete before executing the next code. I've read up on several possible avenues but I have yet to find a good Android example.
public HashMap<String,Boolean> multipleQueries(String collection, String field, final ArrayList<String> criteria) {
HashMap<String,Boolean> resultMap = new HashMap<>();
for (int i = 0; i < criteria.size(); i++){
final int index = i;
db.collection(collection).whereEqualTo(field,criteria.get(i)).limit(1)
.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if(task.isSuccessful()) {
if(task.getResult().size() != 0 ){
resultMap.put(criteria.get(index),true);
} else {
resultMap.put(criteria.get(index),false);
}
} else {
resultMap.put(criteria.get(index),false);
}
}
});
}
return resultMap;
}
Since get() returns a Task, you can use Tasks.whenAll(...).
But you won't be able to return a List from this function, since all data is asynchronously loaded. The best you can do is return the result from Task.whenAll(...), which is itself a Task. See Doug Stevenson's great article on becoming a task master: https://firebase.googleblog.com/2016/10/become-a-firebase-taskmaster-part-4.html

Firestore notice everything has loaded

My Firestore Database structure looks like this:
...a Collection with Routine Objects.
...a Collection with Workout Objects. With the attributes
-> RoutineKey: Stores the Key of the Routine which the Workout is from
-> ExerciseEntryKeys: ArrayList<String> of the Keys of the ExerciseEntry from the Workout
...a Collection with ExerciseEntries Objects.
Now I want to load every Workout from a Routine and the ExerciseEntries of a Workout. To do this, I do the following after I have loaded a Routine Object.
for (final DocumentSnapshot doc : documentSnapshots.getDocuments()) {
final WorkoutSNR workout = doc.toObject(WorkoutSNR.class);
workout.setKey(doc.getId());
workoutsFromRoutine.add(workout);
fm.getColRefExerciseEntries().whereEqualTo("workoutKey", workout.getKey()).get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
if (documentSnapshots.isEmpty()) {
prg.setVisibility(View.GONE);
processData();
} else {
for (int i = 0; i < workout.getExcersiseEntryKeys().size(); i++) {
fm.getDocRefExerciseEntrie(workout.getExcersiseEntryKeys().get(i)).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
final ExcersiseEntrySNR entry = documentSnapshot.toObject(ExcersiseEntrySNR.class);
entry.setKey(documentSnapshot.getId());
workout.getExcersises().add(entry);
processData();
prg.setVisibility(View.GONE);
Collections.sort(workout.getExcersises(), new Comparator<ExcersiseEntrySNR>() {
#Override
public int compare(ExcersiseEntrySNR e1, ExcersiseEntrySNR e2) {
if (e1.getPosition() < e2.getPosition()) {
return -1;
} else if (e1.getPosition() > e2.getPosition()) {
return 1;
} else {
return 0;
}
}
});
}
});
}
}
}
});
}
}
});
This works like it should but as you can see I call:
processData();
prg.setVisibility(View.GONE);
Collections.sort(workout.getExcersises(), new Comparator<ExcersiseEntrySNR>() {
#Override
public int compare(ExcersiseEntrySNR e1, ExcersiseEntrySNR e2) {
if (e1.getPosition() < e2.getPosition()) {
return -1;
} else if (e1.getPosition() > e2.getPosition()) {
return 1;
} else {
return 0;
}
}
});
Evertime an ExerciseEntry has been successfully loaded. This is very unnecessary and I want to call this code only once everything(Every ExerciseEnry for every Workout of an Routine).
What is the best way to notice everything has been loaded? Does Firestore provide any function for this?
I have tried having an Integer that counts the number of successful ExerciseLoads and Workout loads but I can only access final variables inside a nested class(Is that how its called?).
How do I know when the data is completely loaded from the database?
You can add a flag to each Routine and Workout objects with the value of false and once you have downloaded those objects, to set the value to true but this is not how things are working with Firestore. You cannot know when an object from the database is completed downloaded becase Cloud Firestore is also a realtime database and getting data might never complete. That's why is named a realtime database because in any momemnt the data under those Routine and Workout objects can be changed, properties can be added or deleted.
You can use a CompletionListener only when you write or update data and you'll be notified when the operation has been acknowledged by the Database servers but you cannot use this interface when reading data.
So if anyone is wondering what my Solution at the end is, here is my current Code:
fm.getColRefWorkoutSNR().whereEqualTo("routineKey", routineKey).get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
final int workoutSize = documentSnapshots.getDocuments().size();
for (final DocumentSnapshot doc : documentSnapshots.getDocuments()) {
final WorkoutSNR workout = doc.toObject(WorkoutSNR.class);
workout.setKey(doc.getId());
workoutsFromRoutine.add(workout);
fm.getColRefExerciseEntries().whereEqualTo("workoutKey", workout.getKey()).get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
if (documentSnapshots.isEmpty()) {
prg.setVisibility(View.GONE);
processData();
} else {
if (workout.getExcersiseEntryKeys().size() > 0) {
for (int i = 0; i < workout.getExcersiseEntryKeys().size(); i++) {
fm.getDocRefExerciseEntrie(workout.getExcersiseEntryKeys().get(i)).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
final ExcersiseEntrySNR entry = documentSnapshot.toObject(ExcersiseEntrySNR.class);
entry.setKey(documentSnapshot.getId());
workout.getExcersises().add(entry);
if (workout.getExcersises().size() == workout.getExcersiseEntryKeys().size()) {
increaseFullyLoadedWorkouts();
}
if (fullyLoadedWorkouts == workoutSize) {
processData();
prg.setVisibility(View.GONE);
}
}
});
}
} else {
increaseFullyLoadedWorkouts();
if (fullyLoadedWorkouts == workoutSize) {
processData();
prg.setVisibility(View.GONE);
}
}
}
}
});
}
}
});
As you can see, I check if I have loaded every exercise for an workout and if thats the case I increase a "fullyLoadedWorkout" counter. Then I check if the counter equals the workout size and if thats the case I know that I have "fully" loaded my data.
I know thats not a good way to do this but its the only solution I can imagine at the moment. This seems to be way easier in the Realtime Database and I'm still consider switching back to it. Any suggestions for a better way are welcomed.

Retrieve random object from ArrayList

The method below is the method I am using to populate my array. However I wish to return a random deals_informationobject from my ArrayList of type Deals_Information but am not quite sure how.
public void populateArray() {
databaseReference.child("FruitDeals").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Iterable<DataSnapshot> children = dataSnapshot.getChildren();
final ArrayList<Deals_Information> myArray = new ArrayList<>();
for (DataSnapshot child : children) {
Deals_Information deals_information = child.getValue(Deals_Information.class);
myArray.add(deals_information);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Use Random to get a random int from the range of 0 and the size-1 of your collection.
Since Java 1.7, the recommended Random implementation is ThreadLocalRandom.
private int randomInt(final int from, final int to) {
return ThreadLocalRandom.current().nextInt(from, to);
}
Because ArrayLists have a get() function, the way to do this is to first generate a random number by using the math.random() function, and then use the get() function of your ArrayList to call the object at that randomly generated index.

Firebase/Android: Adding retrieved values from Firebase to arraylist returns null pointer exception

I'm trying the add the retrieved values from Firebase database to an Arraylist and from there to a String array. My retrieval method works fine. I can have all the values printed out in a toast. But apparently it doesn't get added to the arraylist.
Here's my code for retrieval in onActivityCreated() of fragment class.
ArrayList<String> allBrands = new ArrayList<>();
brandRef=FirebaseDatabase.getInstance().getReferenceFromUrl("https://stockmanager-142503.firebaseio.com/Brands");
q=brandRef.orderByChild("brandName");
q.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
allBrands.add((dataSnapshot.getValue(Brand.class)).getBrandName());
Toast.makeText(getActivity(),(dataSnapshot.getValue(Brand.class)).getBrandName(), Toast.LENGTH_SHORT).show();
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
And this is where I'm trying to use the arrayList in OnActivityResult() method of the Fragment class but the iterator loop is not executed I believe. The toast is not seen. I'm getting a null pointer exception when I try to work with the array. I assume the values do not get copied to the brands array.
count=allBrands.size();
String[] brands=new String[count];
Iterator<String> itemIterator = allBrands.iterator();
if(itemIterator.hasNext()){
//brands[i] = itemIterator.next();
Toast.makeText(getActivity(), itemIterator.next(), Toast.LENGTH_SHORT).show();
// i++;
}
for( i=0;i<count;i++){
if(brands[i].compareTo(Brand)==0){
f=1;break;
}
}
Here's my database in case that helps. But I can print out all the retrieved values in a Toast with no problem.
It's hard to be certain from the code you shared, by I suspect you may be bitten by the fact that all data is loaded from Firebase asynchronously. Alternatively you may simply not have permission to read the data. I'll give an answer for both.
Data is loaded asynchronously
It's easiest to understand this behavior when you add a few log statements to a minimal snippet of your code:
System.out.println("Before attaching listener");
q.addChildEventListener(new ChildEventListener() {
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
System.out.println("In onChildAdded");
}
public void onChildChanged(DataSnapshot dataSnapshot, String s) { }
public void onChildRemoved(DataSnapshot dataSnapshot) { }
public void onChildMoved(DataSnapshot dataSnapshot, String s) { }
public void onCancelled(DatabaseError databaseError) { }
});
System.out.println("After attaching listener");
The output of this snippet will be:
Before attaching listener
After attaching listener
In onChildAdded (likely multiple times)
This is probably not the order you expected the output in. This is because Firebase (like most cloud APIs) loads the data from the database asynchronously: instead of waiting for the data to return, it continues to run the code in the main thread and then calls back into your ChildEventListener.onChildAdded when the data is available.
There is no way to wait for the data on Android. If you'd do so, your users would get the daunted "Application Not Responding" dialog and your app would be killed.
So the only way to deal with the asynchronous nature of this API is to put the code that needs to have the new data into the onChildAdded() callback (and likely into the other callbacks too at some point):
q.addChildEventListener(new ChildEventListener() {
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
allBrands.add((dataSnapshot.getValue(Brand.class)).getBrandName());
System.out.println(allBrands.length);
}
You need permission to read the data
You need permission to read the data from a location. If you don't have permission, Firebase will immediately cancel the listener. You need to handle this condition in your code, otherwise you'll never know.
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
Try this (I'm writing this to future reference of myself .. too)
As you can see we implement a reresh before it's end. There is probably a nicer way to do it. However, it is not documented. Also all this event Listners should be add autmoatically and released automatically by firebase but they don't do it from some reason.
/**
* #param uid User's ID
* #param Callable send as null just to implement a call to assure the callback is updapted before it's finished
* #return returns ArrayList of all Games unique identification key enlisted in a User
*/
private final ArrayList<String> mGamesPlaying = new ArrayList<>();
public ArrayList<String> mGamesPlaying(final String uid, final Callable refresh) {
final Firebase ref = FirebaseRef;
Firebase usersRef = ref.child("users").child(uid).child("playing_games");
usersRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
mGamesPlaying.clear();
for (DataSnapshot child : snapshot.getChildren()) {
Log.d(TAG, "Test Game" + child.getKey());
mGamesPlaying.add(child.getKey());
}
try {
refresh.call();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
return mGamesPlaying;
}

List size returns 0 after adding object

Can anybody help me figure out this (to me) weird bug. I have been looking at it for hours and still cannot figure it out, I cannot find anything alike on the web aswell.
I am using retrofit2 and OkHttp3 to make some GET API calls. Somehow after I get an response (which is Successful && the body does contain what is has to) and I try to add it to the list, at the end of the loop the list size returns 0.
final List<Object> objectList = new ArrayList<>();
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity);
...
dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
API someAPI = new API();
for (int i = 0; i < listWithIds.size(); i++) {
Call<Object> call = someAPI.getObject(listWithIds.get(i).getId());
final int finalI = i;
call.enqueue(new Callback<Object>() {
#Override
public void onResponse(Call<Object> call, Response<Object> response) {
if (response.isSuccessful()) {
objectList.add(response.body());
}
}
#Override
public void onFailure(Call<Object> call, Throwable t) {
}
});
}
}
});
Queue is asynchronous, you're checking list size inside the loop but outside queue, it's too early. List size is still zero because you're adding object in the callback response.
Try to check list size inside callback delegate.

Categories