How do I wait until my data is retrieved from Firebase? [duplicate] - android

This question already has answers here:
How to return DataSnapshot value as a result of a method?
(6 answers)
Closed 3 years ago.
My problem is that I need to wait for the data from Firebase to be retrieved on the method "onDataChange" before ending my main method.
My code is (CalendarDbHelper) :
public synchronized ArrayList<Day> listDays()
{
final CountDownLatch done = new CountDownLatch(1);
db.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists())
{
for(DataSnapshot postSnapShot:dataSnapshot.getChildren())
{
Day day=postSnapShot.getValue(Day.class);
listDays.add(day);
}
}
done.countDown();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
try {
done.await();
} catch(InterruptedException e) {
e.printStackTrace();
}
return listDays;
}
As you can see, I'm already trying to wait with a CountDownLatch, but it doesn't work : it gets stuck.
I need the listDays for the activity "CalendarActivity" where I use it like this :
//Real initialization of the database
db = FirebaseDatabase.getInstance().getReference();
mDatabase = new CalendarDbHelper(db);
final List<Day> allDays = mDatabase.listDays();
//Activating things to click on
if(allDays.size() > 0){
calendarRV.setVisibility(View.VISIBLE);
calendarRVAdapter = new CalendarAdapter(this, allDays, new CalendarAdapter.OnListItemClickListener() {
#Override
public void onListItemClick(int clickedItemIndex) {
String id = allDays.get(clickedItemIndex).getId();
MyCustomAlertDialog(id);
}
});
calendarRV.setAdapter(calendarRVAdapter);
}else {
calendarRV.setVisibility(View.GONE);
Toast.makeText(this, "There is no product in the database. Start adding now", Toast.LENGTH_LONG).show();
}

if this is your "main method",you should put the return data when the onDataChange is triggered

Try this.
public synchronized ArrayList<Day> listDays()
{
final CountDownLatch done = new CountDownLatch(1);
db.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
if(dataSnapshot.exists())
{
for(DataSnapshot postSnapShot:dataSnapshot.getChildren())
{
Day day=postSnapShot.getValue(Day.class);
listDays.add(day);
}
// Do whatever you want to do with your list from here..
}
done.countDown();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
try {
done.await();
} catch(InterruptedException e) {
e.printStackTrace();
}
return listDays;
}

Related

How to read data from RecyclerView and make comparison?

Hi I want to read the display data from RecyclerView and make comparison.
This my layout for the activity:
What I want to do is to read all data from RecyclerView and compare with Daily Calorie Suggestion.
After reading all data, I need to make comparisons on how many times the user have taken above, less or sufficient total calories as shown in the "Analysis of Total Calories Consumed of Last 7 Days"
The code:
#Override
protected void onStart() {
Query query = ref.orderByChild("timeStamp").limitToLast(7).endAt(Date);
super.onStart();
if (query != null) {
query .addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
userHighlights = new ArrayList<>();
for (DataSnapshot ds : dataSnapshot.getChildren()) {
userHighlights.add(ds.getValue(HighightsModel.class));
requiredCalorieRef = FirebaseDatabase.getInstance().getReference("Users").child(FirebaseAuth.getInstance().getCurrentUser().getUid());
requiredCalorieRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String userCalorieSuggestion = String.valueOf((dataSnapshot.child("daily calorie").getValue()));
int daily_calorie = Integer.parseInt(userCalorieSuggestion);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
HighlightsAdapter highlightsAdapter = new HighlightsAdapter(userHighlights);
highlightsRV.setAdapter(highlightsAdapter);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(UserNewHighlights.this, databaseError.getMessage(),
Toast.LENGTH_SHORT).show();
}
});
}
}
This is my firebase:
Do I have to write a new code to solve this problem or else? Any help will be much appreciated. Thanks
As #MasoudDarzi mentioned, it's not related to the RecyclerView.
You can try something like this:
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
userHighlights = new ArrayList<>();
for (DataSnapshot ds : dataSnapshot.getChildren()) {
userHighlights.add(ds.getValue(HighightsModel.class));
requiredCalorieRef = FirebaseDatabase.getInstance().getReference("Users").child(FirebaseAuth.getInstance().getCurrentUser().getUid());
requiredCalorieRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
String userCalorieSuggestion = String.valueOf((dataSnapshot.child("daily calorie").getValue()));
int daily_calorie = Integer.parseInt(userCalorieSuggestion);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
HighlightsAdapter highlightsAdapter = new HighlightsAdapter(userHighlights);
highlightsRV.setAdapter(highlightsAdapter);
// do calculation here with userHighlights
int countExceeded = 0, countBelow = 0, countSufficient = 0;
for (HighightsModel h : userHighlights) {
if (h.totalCalorie > daily_calorie) {
countExceeded++;
} else if (h.totalCalorie < daily_calorie) {
countBelow++;
} else {
countSufficient++;
}
}
// update your TextView with the count numbers
// todo
}
}
so The brute force solution is to have a for in your data after getting the 7 days average.
for (your 7 days data){
// check if your data is lower or higher than the average
// and store number of higher or lower
}
looking for a better solution?
I have solved my problem by adding another child at History node. Whereby, previously I have only these for History :
but I add new child which is called STATUS, the status is updated from previous activity as seen the pic below:
so what I did for the code is :
private void upDateAnalysisAbove() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("History");
DatabaseReference mRef = ref.child(FirebaseAuth.getInstance().getCurrentUser().getUid());
Query mQuery = mRef.orderByChild("status").equalTo("ABOVE").limitToLast(7);
mQuery.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = String.valueOf(dataSnapshot.getChildrenCount());
int values = Integer.parseInt(value);
txt_above_output.setText(values + " times(s)");
upDateAnalysisLess(values);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
//LESS
private void upDateAnalysisLess(int values) {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("History");
DatabaseReference mRef = ref.child(FirebaseAuth.getInstance().getCurrentUser().getUid());
Query mQuery = mRef.orderByChild("status").equalTo("LESS").limitToFirst(7);
mQuery.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String valueLess = String.valueOf(dataSnapshot.getChildrenCount());
int values_Less = Integer.parseInt(valueLess);
if (values_Less == 0){
txt_less_output.setText(values_Less + " times(s)");
}
if (values > values_Less){
int finalCount = values - values_Less ;
txt_less_output.setText(finalCount + " times(s)");
}
if (values <values_Less){
int finalCount = values_Less - values ;
txt_less_output.setText(finalCount + " times(s)");
}
updateSuff();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
//SUFFICIENT
private void updateSuff() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("History");
DatabaseReference mRef = ref.child(FirebaseAuth.getInstance().getCurrentUser().getUid());
Query mQuery = mRef.orderByChild("status").equalTo("SUFFICIENT").limitToFirst(7);
mQuery.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = String.valueOf(dataSnapshot.getChildrenCount());
int suffValue = Integer.parseInt(value);
if (suffValue == 0) {
txt_sufficient_output.setText(suffValue + " times(s)");
}
if (suffValue != 0){
txt_sufficient_output.setText(suffValue + " times(s)");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Here is the output:
credit to this post that helped me a lot, and thanks for those who tried to help me.

Fetch single value from Firebase against id

I am having problem while fetching a value "rate_perltr" against user id I couldn't fetch so far, I have tried all way but still getting error.
myDatabaseRateFetch = FirebaseDatabase.getInstance().getReference("Customers");
customerName.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
myDatabaseRateFetch.orderByChild("cust_id").equalTo(7040).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
try
{
String ratePerLtr = dataSnapshot.getValue(String.class);
Rate rate = dataSnapshot.getValue(Rate.class);
rate_fetch = rate.getRate_perltr();
}catch (Exception e)
{
Toast.makeText(MainActivity.this, ""+e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(MainActivity.this, ""+databaseError.toString()+"", Toast.LENGTH_SHORT).show();
}
});
rate.setText(Html.fromHtml(" "+ String.valueOf(rate_fetch) +""));
return false;
}
});
}
Rate Model Class
package com.example.wms;
public class Rate {
private double rate_perltr;
public Rate()
{
}
public Rate(double rate_perltr) {
this.rate_perltr = rate_perltr;
}
public double getRate_perltr() {
return rate_perltr;
}
public void setRate_perltr(double rate_perltr) {
this.rate_perltr = rate_perltr;
}
Seems like there is an issue with your Rate Model. It is unable to map your firebase data to your Rate model.
try this one
private void onDataChange(DataSnapshot datasnapshot){
for(DataSnapshot snapshot : datasnapshot.getChildren){
Rate rateFetch = snapshot.getValue(Rate.class);
rateF[0] = ratefetch.getRate_perltr();
}
}
Try this. If you just want to fetch the result of a field from your firebase table, you can do this in your onDataChange() method
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
Rate r = new Rate(); //
try {
r.setVar((String) dataSnapshot.child("var").getValue()); //var is the firebase field
} catch (Exception e) {
r.setVar("whatever you want here");
}
}
If you want to fill a model with the firebase result, ensure that instance variables names matches the fields in the firebase table then you can do this:
public void onDataChange(#NonNull DataSnapshot datasnapshot){
for(DataSnapshot snapshot : datasnapshot.getChildren){
Rate rate = snapshot.getValue(Rate.class);
//then get any variable you want from rate
}
}

Can't convert object of type java.lang.String to type

I have a problem after I add another table to my firebase database. it gives me following error:
com.google.firebase.database.DatabaseException: Can't convert object of type java.lang.String to type ...
here is my code, which works fine if I have database like this:
and code:
Main activity code:
rvOrder = (RecyclerView) findViewById(R.id.rvOrders);
rvOrder.setLayoutManager(new LinearLayoutManager(this));
databaseReference= FirebaseDatabase.getInstance().getReference();
firebaseHelper=new FirebaseHelper(databaseReference);
myAdapter=new MyAdapter(this,firebaseHelper.retrieve());
rvOrder.setAdapter(myAdapter);
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
rvOrder.setAdapter(myAdapter);
myAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Firebase helper code:
public class FirebaseHelper {
DatabaseReference databaseReference ;
Boolean saved = null;
ArrayList<Order> orders = new ArrayList<>();
public FirebaseHelper(DatabaseReference databaseReference) {
this.databaseReference = databaseReference;
}
public Boolean save(Order order) {
if (order == null) {
saved = false;
} else {
try {
databaseReference.child("Orders").push().setValue(order);
saved = true;
} catch (DatabaseException e) {
e.printStackTrace();
saved = false;
}
}
return saved;
}
private void fetchData(DataSnapshot dataSnapshot) {
orders.clear();
for(DataSnapshot ds : dataSnapshot.getChildren()) {
System.out.println(ds.getValue(Order.class));
Order order = ds.getValue(Order.class);
orders.add(order);
}
}
public ArrayList<Order> retrieve() {
databaseReference.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return orders;
}
If I change my code to this (which I saw in this question link to the question ):
in Main activity:
databaseReference= FirebaseDatabase.getInstance().getReference();
to
databaseReference= FirebaseDatabase.getInstance().getReference().child("Orders");
and in firebasehelper:
for(DataSnapshot ds : dataSnapshot.getChildren()) {
System.out.println(ds.getValue(Order.class));
Order order = ds.getValue(Order.class);
orders.add(order);
}
to
System.out.println(dataSnapshot.getValue(Order.class));
Order order = dataSnapshot.getValue(Order.class);
orders.add(order);
then I am able to add another tables to database but I just get only the last item from the Orders table.
I want to add many tables with multiple items into the database and also I want to get all items from the "Orders" table.
Can anybody suggest me anything?
In your first code snippet you read from the root of the database. Since you're trying to read orders, you should read from /Orders instead:
databaseReference= FirebaseDatabase.getInstance().getReference();
firebaseHelper=new FirebaseHelper(databaseReference);
//myAdapter=new MyAdapter(this,firebaseHelper.retrieve());
//rvOrder.setAdapter(myAdapter);
databaseReference.child("Orders").addValueEventListener(new ValueEventListener() {
Now in your onDataChange you can read the orders by looping over them. Since you already do precisely that in FirebaseHelper.fetchData, you can call that method:
public void onDataChange(DataSnapshot dataSnapshot) {
firebaseHelper.fetchData(dataSnapshot);
}
Now all that is left is to wire up the data from firebaseHelper.orders to an adapter and the view:
public void onDataChange(DataSnapshot dataSnapshot) {
firebaseHelper.fetchData(dataSnapshot);
myAdapter=new MyAdapter(this,firebaseHelper.orders);
rvOrder.setAdapter(myAdapter);
}
This last step will require that you make FirebaseHelper.orders public, and probably some of the variables must be final.
I think you need to update this method accordingly:
public Boolean save(Order order) {
if (order == null) {
saved = false;
} else {
try {
databaseReference.child("Orders").push().setValue(order);
saved = true;
} catch (DatabaseException e) {
e.printStackTrace();
saved = false;
}
}
return saved;
}
Try to change:
databaseReference.child("Orders").push().setValue(order);
To:
databaseReference.child("Orders").child(order.getId).setValue(order);
Also, to retreive all Orders keep using the enhanced loop:
for(DataSnapshot ds : dataSnapshot.getChildren()) {
System.out.println(ds.getValue(Order.class));
Order order = ds.getValue(Order.class);
orders.add(order);
}

How do I rework these Firebase ValueEventListeners to be readable?

If there's something I'm struggling with it's how to write good looking code with asynchronous calls to Firebase. Below is the code for one of my functions that retrieves all users and the current user from Firebase and checks the distance between their chosen locations.
I can't really move each of the ValueEventListeners to separate functions since they all have to fire in succession.
How do I make this code look good?
private void getUsersToDisplay() {
// Get users to display from firebase
Log.w(TAG, "User fetch initialized");
DatabaseReference currentUserRef = mDatabase.getReference().child("users/" + mCurrentUser.getUid());
// Get current user from firebase
currentUserRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot currentUserSnapshot) {
// Get the users chosen location for use in distance calculation
final Double currentUserChosenLatitude =
Double.parseDouble(currentUserSnapshot.child("chosen_location/latlng/latitude").getValue().toString());
final Double currentUserChosenLongitude =
Double.parseDouble(currentUserSnapshot.child("chosen_location/latlng/longitude").getValue().toString());
DatabaseReference usersRef = mDatabase.getReference().child("users");
// Get all users from firebase
usersRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot allUsersSnapshot) {
// For all users in firebase
for (DataSnapshot userSnap : allUsersSnapshot.getChildren()) {
String userId = userSnap.getKey();
DatabaseReference userRef = mDatabase.getReference().child("users/" + userId);
// If the user isn't the current user
if (!userId.equals(mCurrentUser.getUid())) {
// Get the user's info from firebase
userRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot userSnapshot) {
Double userChosenLatitude =
Double.parseDouble(userSnapshot.child("chosen_location/latlng/latitude").getValue().toString());
Double userChosenLongitude =
Double.parseDouble(userSnapshot.child("chosen_location/latlng/longitude").getValue().toString());
float[] results = new float[1];
Location.distanceBetween(
currentUserChosenLatitude,
currentUserChosenLongitude,
userChosenLatitude,
userChosenLongitude,
results);
// If the user is within 10km of the current user, display it
if (results[0] < 10000) {
users.put(userSnapshot.getKey(), (String) userSnapshot.child("first_name").getValue());
mUserAdapter.add((String) userSnapshot.child("first_name").getValue());
Log.w(TAG, "User to display fetch complete");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "User to display fetch cancelled");
}
});
}
}
Log.w(TAG, "Users fetch completed");
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "Users fetch cancelled");
}
});
Log.w(TAG, "Current user fetch complete");
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "Current user fetch cancelled");
}
});
}
I write my code like this so it easier to read:
onCreate() {
dataRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
doSomething(dataSnapshot);
...
}
...
}
}
doSomething(DataSnapshot dataSnapshot) {
...
}
If I want a Firebase call to run after another Firebase call, I place it inside doSomething.
BUT, if that call doesn't have to run after each other (like "get current user" and "get all user" call from your sample code), I made it like this:
boolean firstCallDone = false;
boolean secondCallDone = false;
DataSnapshot firstDataSnapshot = null;
DataSnapshot secondDataSnapshot = null;
onCreate() {
firstRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
firstCallDone = true;
firstDataSnapshot = dataSnapshot;
if (firsCallDone && secondCallDone)
doSomething();
}
...
}
secondRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
secondCallDone = true;
secondDataSnapshot = dataSnapshot;
if (firsCallDone && secondCallDone)
doSomething();
}
...
}
}
doSomething() {
// do something with firstDataSnapshot and secondDataSnapshot
...
}
Hope this help.

Why When i get the data From Firebase it return NULL?

I'm trying to retrieve user's data from firebase using search by Email to find the user ID and use that ID to get the user's node children so i used that code to do that:
mSignin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mEmailStr = SignupActivityFragment.removeSpaces(mEmail.getText().toString());
mPasswordStr = mPassword.getText().toString();
mAuth.signInWithEmailAndPassword(mEmailStr, mPasswordStr).addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(Task<AuthResult> task) {
if (task.isSuccessful()){
getExistingUserInfo();
if(User!=null) {
sglstr = User.get("sgl");
if (sglstr.equals("true")) {
Intent intent = new Intent(getActivity(), ControllerSGL.class);
intent.putExtra("user", User);
startActivity(intent);
} else if (sglstr.equals("false")) {
Intent intent = new Intent(getActivity(), ControllerStudent.class);
intent.putExtra("user", User);
startActivity(intent);
}
}
else {
Toast.makeText(getActivity(), "NULL!!!!!!", Toast.LENGTH_SHORT).show();
}
Toast.makeText(getActivity(), "Success", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(getActivity(), "Not Success", Toast.LENGTH_SHORT).show();
}
}
});
}
});
and getExistingUserInfo Method
public void getExistingUserInfo(){
FirebaseDatabase database = FirebaseDatabase.getInstance();
database.getReference().child("allUsers").orderByChild("emailAddress")
.equalTo(mEmailStr)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot childSnapshot : dataSnapshot.getChildren()) {
uid = childSnapshot.getKey();
Log.v("HimaAbosalem",uid);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
database.getReference("allUsers").child(uid).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//get user data from dataSnapshot
User=(HashMap<String,String>)dataSnapshot.getValue();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
I need to use HashMap to send the data throw intent, but i gives me that java.lang.NullPointerException: Can't pass null for argument 'pathString' in child() at this part of getExistingUserInfo method
database.getReference("allUsers").child(uid).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//get user data from dataSnapshot
User=(HashMap<String,String>)dataSnapshot.getValue();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
i thought the problem was uid value equal null but i made a log and it's not, and i tried to send that uid throw an intent to another activity and i recieved it to found it's not null but when i tried to get the data in that new Activity it gives the same Error!!!
I solved this problem by divide the getExistingUserInfo to two separate Function and call the function that get allUsers at onDataChange to guarantee that user's id returns
that's code after Edited:
public void getExistingUserInfo(){
FirebaseDatabase database = FirebaseDatabase.getInstance();
database.getReference().child("allUsers").orderByChild("emailAddress")
.equalTo(mEmailStr)
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot childSnapshot : dataSnapshot.getChildren()) {
uid = childSnapshot.getKey();
Log.v("HimaAbosalem",uid);
}
getUser(uid);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
public void getUser(String uid){
database.getReference("allUsers").child(uid).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//get user data from dataSnapshot
User=(HashMap<String,String>)dataSnapshot.getValue();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}

Categories

Resources