Synchronization problem while getting data from firebase database - java

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.

Related

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

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

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!

How can I populate an ExpandableListView child views through Firebase database?

I want to populate my expandable list view with the values from Firebase database. I am able to add data to views statically but through Firebase not.
This is my code for that:
public class ExpandableListitems {
public List<String> food;
public HashMap<String, List<String>> getData() {
final HashMap<String, List<String>> expandableListDetail = new HashMap<String, List<String>>();
//getting fooditems from database
FirebaseDatabase firebaseDatabasefighter = FirebaseDatabase.getInstance();
DatabaseReference databaseReferencefighter = firebaseDatabasefighter.getReference("food").child("food_details");
ValueEventListener valueEventListenerfighter = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
food = new ArrayList<String>();
String value = String.valueOf(ds.getValue());
food.add(value);
Log.i("vipan",value);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
};
databaseReferencefighter.addValueEventListener(valueEventListenerfighter);
food.add("milk");
food.add("dahi");
food.add("paratha");
food.add("aaloo");
expandableListDetail.put("FoodItems", food);
return expandableListDetail;
And my database looks like this:
food
food_details:
"2% Fat Milk"
i want to add this food_details value into the listview's view but unable so far.How can i implement this?
You cannot return something now that hasn't been loaded yet. With other words, you cannot simply return the expandableListDetail HashMap<String, List<String>> which contains a list of strings that is coming from the database as a result of a method because this list will always be empty due the asynchronous behaviour of this method. This means that by the time you are trying to return that result, 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 the food list only inside the onDataChange() method, 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.

Changing firebase data according to query

Code
Query mSeenRef = mDatabaseReference.child("Messages").child(MessageRecieverId).child(MessageSenderId).orderByChild("From").equalTo(MessageRecieverId);
This is the query and I want to change another child if this query is true... Like similar to adding value event listener and fetching this above querys data... just that instead of fetching i want to change data but I cant use setValue method inside value event listener so what should I do?
I also have UIDS inside MessageRecieverId and inside that is From
Try the following:
DatabaseReference db = FirebaseDatabase.getInstance().getReference();
Query mSeenRef = db.child("Messages").child(MessageRecieverId).child(MessageSenderId).orderByChild("From").equalTo(MessageRecieverId);
mSeenRef.addValueEventListener(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot dataSnapshot){
if(dataSnapshot.exist()) {
for(DataSnapshot ds: dataSnapshot.getChildren()){
String keyId=ds.getKey();
DatabaseReference ref= db.child("Messages").child(MessageRecieverId).child(MessageSenderId).child(keyId);
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("Seen", true);
ref.updateChildren(childUpdates);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
This will check if the query exists in the database using dataSnapshot.exists(), then if this query exists, you will be able to change the value of a child. You need to use the method updateChildren() to update only one attribute.
Check here for more information:
https://firebase.google.com/docs/database/android/read-and-write#update_specific_fields

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