Using variable from onComplete() method while working with Firestore database - java

So I have a collection in Cloud Firestore and I need to store the informations from database in a list (coordPlaces). I did it like this, created these methods:
private void readData(FirestoreCallback firestoreCallback){
db.collection("AddPlaces")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if(task.isSuccessful()){
if(task.getResult() != null){
List<ListPlacesModel> list = task.getResult().toObjects(ListPlacesModel.class);
for(ListPlacesModel model : list)
{
LatLng place = new LatLng(model.getPlaceLat(), model.getPlaceLng());
coordPlaces.add(place);
String name = model.getPlaceName();
gMap.addMarker(new MarkerOptions().position(place).title(name));
}
firestoreCallback.onCallback(coordPlaces);
}
}
else {
Log.d("ERROR", String.valueOf(task.getException()));
}
}
});
}
private interface FirestoreCallback{
void onCallback(List<LatLng> myList);
}
And then called the method like this:
db = FirebaseFirestore.getInstance();
readData(new FirestoreCallback() {
#Override
public void onCallback(List<LatLng> myList) {
});
I've saw this solution in this video.
But now I need to access this list in order to creathe a 2D array out of it, in this manner:
for(int i=0;i<coordPlaces.size();i++)
{
for(int j=0;j<coordPlaces.size();j++)
{
int distance = (int) SphericalUtil.computeDistanceBetween(coordPlaces.get(i), coordPlaces.get(i+1));
graph[i][j] = distance;
}
}
Anywhere in the code where I put these 2 for loops, my app crashes. I can't find a solution of how to create this 2D array.
Any ideas?

The only places where you should add those lines of code should be either inside onComplete() or if you're using a custom callback inside onCallback(). If you place those lines in any other parts of your class, you'll get an empty list, hence that IndexOutOfBoundsException.
If you don't want to use that approach, you might also consider using a LiveData object, as it's a type of object that can be observed. If you understand Kotlin, the following resource will help:
How to read data from Cloud Firestore using get()?

Related

How to change an array value with a method call in Android when using Firebase and Android

I have the following method in Android (implemented in Java):
public static void getInformationWhenInfoButtonIsPressed (FirebaseRatingCallback callback,float [] ratingValue, String item) {
DatabaseReference rootRef_Firebase;
rootRef_Firebase = FirebaseDatabase.getInstance(DataBaseEntries.FIREBASE_URL).getReference();
rootRef_Firebase
.child(DataBaseEntries.FIREBASE_NODE_RATINGS_AGGREGATED)
.orderByChild(DataBaseEntries.FIREBASE_NAME)
.equalTo(item)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int numberOfRatingsOverall = 0;
double tempScoreOverall =0;
for (DataSnapshot ds: dataSnapshot.getChildren()) {
float averageRatingOfCurrentDrink = 0;
int numberOfRatingsCurrentDrink = 0;
if (ds.child(DataBaseEntries.FIREBASE_AVERAGE_RATING).getValue(Float.class)!=null) {
averageRatingOfCurrentDrink= ds.child(DataBaseEntries.FIREBASE_AVERAGE_RATING).getValue(Float.class);
}
if (ds.child(DataBaseEntries.FIREBASE_NUMBER_OF_RATINGS).getValue(Integer.class)!=null) {
numberOfRatingsCurrentDrink = ds.child(DataBaseEntries.FIREBASE_NUMBER_OF_RATINGS).getValue(Integer.class);
numberOfRatingsOverall= numberOfRatingsOverall + numberOfRatingsCurrentDrink ;
}
tempScoreOverall = tempScoreOverall + numberOfRatingsCurrentDrink * averageRatingOfCurrentDrink;
}
ratingValue[0] = (float) (tempScoreOverall/numberOfRatingsOverall);
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
throw error.toException();
}
});
ratingValue[0] = (float) (Math.round(ratingValue[0] * 10.0) / 10.0);
}
And within the same class I have the following interface:
public interface FirebaseRatingCallback {
void onCallBackRating (boolean dataIsReadFromFirebase); }
The method should change the value of the reference data type array ratingValue[0]. Unfortunately Firebase Realtime database works asynchronously so I have to use a callback which I do using FirebaseRatingCallback callback. But I don't know how to do this using a ValueEventListener as I am doing in this example. I know how to do this with a OnCompleteListener because there you have the method public void onComplete(#NonNull Task<Void> task) { which I can't use when having a ValueEventListener.
So I tried to add the following but this does not work:
public static void getInformationWhenInfoButtonIsPressed (FirebaseRatingCallback callback,float [] ratingValue, String item) {
DatabaseReference rootRef_Firebase;
rootRef_Firebase = FirebaseDatabase.getInstance(DataBaseEntries.FIREBASE_URL).getReference();
rootRef_Firebase
.child(DataBaseEntries.FIREBASE_NODE_RATINGS_AGGREGATED)
.orderByChild(DataBaseEntries.FIREBASE_NAME)
.equalTo(item)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int numberOfRatingsOverall = 0;
double tempScoreOverall =0;
for (DataSnapshot ds: dataSnapshot.getChildren()) {
float averageRatingOfCurrentDrink = 0;
int numberOfRatingsCurrentDrink = 0;
if (ds.child(DataBaseEntries.FIREBASE_AVERAGE_RATING).getValue(Float.class)!=null) {
averageRatingOfCurrentDrink= ds.child(DataBaseEntries.FIREBASE_AVERAGE_RATING).getValue(Float.class);
}
if (ds.child(DataBaseEntries.FIREBASE_NUMBER_OF_RATINGS).getValue(Integer.class)!=null) {
numberOfRatingsCurrentDrink = ds.child(DataBaseEntries.FIREBASE_NUMBER_OF_RATINGS).getValue(Integer.class);
numberOfRatingsOverall= numberOfRatingsOverall + numberOfRatingsCurrentDrink ;
}
tempScoreOverall = tempScoreOverall + numberOfRatingsCurrentDrink * averageRatingOfCurrentDrink;
}
public void onComplete(#NonNull Task<Void> task) {
ratingValue[0] = (float) (tempScoreOverall/numberOfRatingsOverall);
callback.onCallBackRating(true);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
throw error.toException();
}
});
}
Does anyone know how to do this?
I know how to do this when writing into the database, but I don't know how to do this when reading something out of it.
The simplest solution would be to call get() on a query object, and once the operation is complete, update the LiveData object. After that, simply observe the change in the UI.
From the documentation of Query#get():
Executes the query and returns the results as a QuerySnapshot.
So once the query is complete, there will be no other future events, even if the database changes. So there will be nothing that will affect your UI after the query is complete.
Edit:
Is it not possible to do this without LiveData?
There are other methods to do that, but this is by far the simplest one. Callbacks are not recommended to be used anymore.
When using LiveData I need a lot of code because I'd also need 2 additional classes (ViewModel and LiveData class).
What's wrong with that? That's programming. Regarding, a ViewModel that's the way the Android team recommends building the apps.
not do something completely new with LiveData and ViewModel as this would also take too much time and increase the complexity because of the many classes.
It won't take much time. It will take a matter of minutes. As soon as you understand those concepts, you'll be a better Android developer. Don't be afraid to learn something new.
P.S. Java is still a great language, but for Android development, I recommend Kotlin. What you are trying to achieve can be written in Kotlin using a single line of code.

Synchronization problem while getting data from firebase database

I have a problem with my Firebase database data retrieve sync. I am able to get data but return works before it. So my data never put in the intended list.
Here is my code:
DatabaseManager databaseManager = new DatabaseManager();
MedicineData medicineData = new MedicineData();
boolean validated = false;
private static final String TAG = "BarcodeDecoderDataMatrix";
public Map getDataMatrix(String dataMatrixText) {
Map<String, Object> dataMatrix = new HashMap<>();
String barcodeNum = getBarcodeNumber(dataMatrixText);
String expireDate = getExpireDate(dataMatrixText);
String serialNum = getSerialNumber(dataMatrixText);
String partyNum = getPartyNumber(dataMatrixText);
dataMatrix.put("barcodeNumber",barcodeNum);
dataMatrix.put("expireDate", expireDate);
dataMatrix.put("serialNumber", serialNum);
dataMatrix.put("partyNumber", partyNum);
getMedicineName(barcodeNum, (success) -> {
if(success){
//find the data on database
dataMatrix.put("medicineName", medicineData.getProductName());
dataMatrix.put("companyName", medicineData.getCompanyName());
dataMatrix.put("price", medicineData.getPrice());
}
else {
//can't find on database
}
});
return dataMatrix;
}
This method called from another class to get dataMatrix list.
private void getMedicineName(String barcodeNum, SimpleCallback<Boolean> finishedCallback) {
DatabaseReference rootRef = databaseManager.getReference();
DatabaseReference medicinesRef = rootRef.child("MedicineList");
Query queryMedicineNameFinder = medicinesRef.orderByKey().equalTo(barcodeNum);
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
medicineData = ds.getValue(MedicineData.class);
}
if (medicineData != null){
validated = true;
}
finishedCallback.run(validated);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage()); //Don't ignore errors!
finishedCallback.run(false);
}
};
queryMedicineNameFinder.addListenerForSingleValueEvent(valueEventListener);
}
These two methods are in the same class called BarcodeDecoderDataMatrix. I create an instance of this class from another class and call getDataMatrix method. I am expecting to get a list include my values. I can get barcodeNum, expireDate, serialNum, partyNum values without a problem. But the list doesn't include medicineName, companyName and price informations. I made a debug so I know I can get data from database. I can see it in medicineData variable. I am pretty sure it is a sync issue. Because my data in medicineData but it can't put it in the list before return called.
How can I make this happen?
addListenerForSingleValueEvent() method that you are using in your code, is asynchronous. This means that it returns immediately, while onDataChange() method fires only when the results are available.
What you are doing in your code is incorrect because you assuming that the results are immediately available and are not.
When you call getMedicineName() method, you should directly use dataMatrix in the callback, after the data is known that is available.
For more information, 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.

ArrayList<Object> copied from another ArrayList is empty even if there is data [duplicate]

This question already has answers here:
How to return DataSnapshot value as a result of a method?
(6 answers)
Closed 3 years ago.
I add items to a list through a Model class and then add these items to an ArrayList<Object>. However, I always get a size of zero even if the commentsList.size() > 1.
I tried to convert the List to ArrayList and tried adding items. But always the size was 0 on ArrayList<Object>.
ArrayList<Comments> commentsList = new ArrayList<>(Arrays.asList(new Comments("username", "time", "date")));
Here are the functions that I am using.
private ArrayList<Object> getObject() {
if (getComments() != null && getComments().size()>=1) {
objects.add(getComments().get(0));
}
return objects;
}
public static ArrayList<Comments> getComments() {
ArrayList<Comments> commentsList = new ArrayList<>();
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Comments");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
commentsList.clear();
for (DataSnapshot shot : snapshot1.getChildren()) {
Comments comments = shot.getValue(Comments.class);
commentsList.add(comments);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return commentsList;
}
In the getComments function, you are doing an Asynchronous operation which is retrieving the data from your firebase database. Hence you do not actually have anything in your commentsList. The function is just initializing a new ArrayList with zero elements, of course, and creating a ValueEventListener which is waiting for the data to be received in your application. However, this operation is asynchronous (running in a separate thread) and hence after creating the ValueEventListener, the function is returning immediately with the empty list. Thus you are getting an empty list as well when you are trying to build the ArrayList<Object> in your getObject function.
I would suggest writing another function which will be invoked when the onDataChange function is called after receiving the data from your firebase database. For example, write a function in the same class as the following.
void nowCreateArrayListOfObjects() {
// TODO: Call the getObject function here
}
Now call this function from your onDataChange function as follows.
public static ArrayList<Comments> getComments() {
ArrayList<Comments> commentsList = new ArrayList<>();
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Comments");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
commentsList.clear();
for (DataSnapshot shot : snapshot1.getChildren()) {
Comments comments = shot.getValue(Comments.class);
commentsList.add(comments);
}
// Invoke the function here to get the values now
nowCreateArrayListOfObjects();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return commentsList;
}
Hope that helps!

Firestore - Merging two queries locally

Since there is no logical OR operator in Firestore, I am trying to merge 2 separate queries locally.
Now I wonder how I can keep up the proper order of the results. When I run 2 queries independently, I can't oder the results specificly (at least not the order in which I get the results from Firestore with the orderBy method).
My idea was to put the 2nd query inside the onSuccessListener of the 1st query. Is this a bad idea performance wise?
public void loadNotes(View v) {
collectionRef.whereLessThan("priority", 2)
.orderBy("priority")
.get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
Note note = documentSnapshot.toObject(Note.class);
//adding the results to a List
}
collectionRef.whereGreaterThan("priority", 2)
.orderBy("priority")
.get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (QueryDocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
Note note = documentSnapshot.toObject(Note.class);
//adding the results to a List
}
}
});
}
});
}
To merge 2 separate queries locally, I recommend you to use Tasks.whenAllSuccess() method. You can achieve this, using the following lines of code:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
Query firstQuery = rootRef...
Query secondQuery = rootRef...
Task firstTask = firstQuery.get();
Task secondTask = secondQuery.get();
Task combinedTask = Tasks.whenAllSuccess(firstTask, secondTask).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
#Override
public void onSuccess(List<Object> list) {
//Do what you need to do with your list
}
});
As you can see, when overriding the onSuccess() method the result is a list of objects which has the exact order of the tasks that were passed as arguments into the whenAllSuccess() method.
There is also another approach and that would be to use Tasks.continueWith() method. But according to the use-case of your app, you can use eiter whenAllSuccess() method or continueWith() method. Please see here the official documentation.

Setting Singleton property value in Firebase Listener

I'm currently testing out Firebase along with a Singleton model I plan to use to access during the lifecycle of the whole app. I'm now stuck with something that seems really trivial but I can't figure it out for the life of me. I have a sample of the model I use: Bookmarks in firebase.
public class BookSingleton {
private static BookSingleton model;
private ArrayList<BookMark> bookmarks = new ArrayList<BookMark>();
public static BookSingleton getModel()
{
if (model == null)
{
throw new IllegalStateException("The model has not been initialised yet.");
}
return model;
}
public ArrayList<Bookmark> theBookmarkList()
{
return this.bookmarks;
}
public void setBookmarks(ArrayList<Bookmark> bookmarks){
this.bookmarks = bookmarks;
}
public void loadModelWithDataFromFirebase(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//getting all properties from firebase...
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
}
}
//bookmarks still exist here at this point
setBookmarks(loadedBookmarks);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
//by now loadedBookmarks is empty
//this is probably the issue?
//even without this line bookmarks is still not set in mainactivity
setBookmarks(loadedBookmarks);
}
Now when I start the mainActivity with the instance of the Singleton set I get a null error because clearly the function I wrote to load the model data from firebase sets nothing.
Something like this:
MainActivity
public class MainActivity extends AppCompatActivity {
private BookSingleton theModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the model
theModel = BookSingleton.getModel(this);
//manually setting this works
// ArrayList<Book> bookSamples = new ArrayList<Book>;
// bookSamples.add(aBookSample);
theModel.loadModelWithSampleData(bookSamples);
//should have set the singleton model property Bookmarks to the results from firebase
theModel.loadModelWithDataFromFirebase();
//returns 0
Log.d(TAG, "" + theModel.theBookmarkList().size());
setContentView(R.layout.activity_main);
//......rest of code
How can I make this work?
Firebase loads and synchronizes data asynchronously. So your loadModelWithDataFromFirebase() doesn't wait for the loading to finish, it just starts loading the data from the database. By the time your loadModelWithDataFromFirebase() function returns, the loading hasn't finished yet.
You can easily test this for yourself with some well-placed log statements:
public void loadModelWithDataFromFirebase(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
Log.v("Async101", "Start loading bookmarks");
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.v("Async101", "Done loading bookmarks");
//getting all properties from firebase...
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
}
#Override
public void onCancelled(FirebaseError error) { throw error.toException(); }
});
Log.v("Async101", "Returning loaded bookmarks");
setBookmarks(loadedBookmarks);
}
Contrary to what you likely expect, the order of the log statements will be:
Start loading bookmarks
Returning loaded bookmarks
Done loading bookmarks
You have two choice for dealing with the asynchronous nature of this loading:
squash the asynchronous bug (usually accompanied by muttering of phrases like: "it was a mistake, these people don't know what they're doing")
embrace the asynchronous beast (usually accompanied by quite some hours of cursing, but after a while by peace and better behaved applications)
Take the blue pill - make the asynchronous call behave synchronously
If you feel like picking the first option, a well placed synchronization primitive will do the trick:
public void loadModelWithDataFromFirebase() throws InterruptedException {
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
Semaphore semaphore = new Semaphore(0);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
semaphore.release();
}
#Override
public void onCancelled(FirebaseError error) { throw error.toException(); }
});
semaphore.acquire();
setBookmarks(loadedBookmarks);
}
Update (20160303): when I just tested this on Android, it blocked my app. It works on a regular JVM fine, but Android is more finicky when it comes to threading. Feel free to try and make it work... or
Take the red pill - deal with the asynchronous nature of data synchronization in Firebase
If you instead choose to embrace asynchronous programming, you should rethink your application's logic.
You currently have "First load the bookmarks. Then load the sample data. And then load even more."
With an asynchronous loading model, you should think like "Whenever the bookmarks have loaded, I want to load the sample data. Whenever the sample data has loaded, I want to load even more."
The bonus of thinking this way is that it also works when the data may be constantly changing and thus synchronized multiple times: "Whenever the bookmarks change, I want to also load the sample data. Whenever the sample data changes, I want to load even more."
In code, this leads to nested calls or event chains:
public void synchronizeBookmarks(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
setBookmarks(loadedBookmarks);
loadSampleData();
}
#Override
public void onCancelled(FirebaseError error) { throw error.toException(); }
});
}
In the above code we don't just wait for a single value event, we instead deal with all of them. This means that whenever the bookmarks are changed, the onDataChange is executed and we (re)load the sample data (or whatever other action fits your application's needs).
To make the code more reusable, you may want to define your own callback interface, instead of calling the precise code in onDataChange. Have a look at this answer for a good example of that.
TL;DR: Embrace Firebase Asynchronicity
As I mentioned in another post, you can deal with the asynchronous nature of Firebase using promises. It would be like this:
public Task<List<Data>> synchronizeBookmarks(List<Bookmark> bookmarks) {
return Tasks.<Void>forResult(null)
.then(new GetBook())
.then(new AppendBookmark(bookmarks))
.then(new LoadData())
}
public void synchronizeBookmarkWithListener() {
synchronizeBookmarks()
.addOnSuccessListener(this)
.addOnFailureListener(this);
}
com.google.android.gms.tasks
Google API for Android provides a task framework (just like Parse did with Bolts), which is similar to JavaScript promises concept.
First you create a Task for downloading the bookmark from Firebase:
class GetBook implements Continuation<Void, Task<Bookmark>> {
#Override
public Task<Bookmark> then(Task<Void> task) {
TaskCompletionSource<Bookmark> tcs = new TaskCompletionSource();
Firebase db = new Firebase("url");
Firebase bookmarksRef = db.child("//access correct child");
bookmarksRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
tcs.setResult(dataSnapshot.getValue(Bookmark.class));
}
});
tcs.getTask();
}
}
Now that you got the idea, supose that setBookmarks and loadSampleData are also asynchronous. You also can create them as Continuation tasks (just like the previous one) that will run in sequence:
class AppendBookmark(List<Bookmark> bookmarks) implements
Continuation<List<Bookmark>, Task<Bookmark> {
final List<Bookmark> bookmarks;
LoadBookmarks(List<Bookmark> bookmarks) {
this.bookmark = bookmark;
}
#Override
Task<List<Bookmark>> then(Task<Bookmark> task) {
TaskCompletionSource<List<Bookmark>> tcs = new TaskCompletionSource();
bookmarks.add(task.getResult());
tcs.setResult(this.bookmarks);
return tcs.getTask();
}
}
class LoadSampleData implements Continuation<List<Bookmark>, List<Data>> {
#Override
public Task<List<Data>> then(Task<List<Bookmark>> task) {
// ...
}
}
You have to initialize your Singleton when the class is loaded.
Put this on your code:
private static BookSingleton model = new BookSingleton();
private BookSingleton() {
}
public static BookSingleton getModel() { return model == null ? new BookSingleton() : model;
}

Categories

Resources