0

In my Realtime Database (Firebase) i have two 'collections' one for essen(food) and one for bewertung (rating of the foods)

I try to display the data for each food in my app, which works for the name of the food an the price (Preis)

therefore i calculate the average rating for each food, which works according to the values which are displayed in the console

but when i try to access the average of each food, to set it to an TextView the average is 0.0, which is the default float-value. But every other value of a food is displayed correctly (name and price)

Here is my fragment java class where i calculate the average and set all values i retrieve from firebase:

public class Aktuelle_Bewertungen_Fragment extends Fragment{


    //Elemente:
    private RecyclerView recyclerView_aktuelle_Bewertungen;


    private View aktuelleBewertungenView;

    private DatabaseReference essenRef;
    private DatabaseReference bewertungenRef;



    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        aktuelleBewertungenView =  inflater.inflate(R.layout.fragment_aktuelle_bewertungen, container, false);

        //Elemente finden:
        recyclerView_aktuelle_Bewertungen = (RecyclerView) aktuelleBewertungenView.findViewById(R.id.recyler_aktuelle_bewertungen);

        //Referenz auf essen:
        essenRef = FirebaseDatabase.getInstance().getReference().child("essen");
        //Referenz auf bewertungen:
        bewertungenRef = FirebaseDatabase.getInstance().getReference().child("bewertung");


        return aktuelleBewertungenView;
    }



    @Override
    public void onStart() {

        super.onStart();

        FirebaseRecyclerOptions options = new FirebaseRecyclerOptions.Builder<essen>().setQuery(essenRef, essen.class).build();
        final FirebaseRecyclerAdapter<essen, EssenViewHolder> adapter = new FirebaseRecyclerAdapter<essen, EssenViewHolder>(options) {

            @NonNull
            @Override
            public EssenViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.reihen, parent, false);
                EssenViewHolder viewHolder = new EssenViewHolder(view);

                return viewHolder;

            }

            @Override
            protected void onBindViewHolder(@NonNull final EssenViewHolder holder, final int position, @NonNull essen model) {


                final ArrayList<essen> testessen =new ArrayList<>();

                essenRef.addValueEventListener(new ValueEventListener() {

                    @Override
                    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                        for (DataSnapshot ds : dataSnapshot.getChildren()){

                                final essen e = new essen();
                                  e.name =   ds.child("name").getValue(String.class);
                                  e.Preis = ds.child("Preis").getValue(String.class);


                            bewertungenRef.addValueEventListener(new ValueEventListener() {
                                @Override
                                public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                                    float sum = (float) 0.0;
                                    int counter = 0;


                                    for (DataSnapshot ds1 : dataSnapshot.getChildren()){
                                        if (ds1.child("eid").getValue(String.class).equals(e.name)){

                                            sum = sum + ds1.child("wert").getValue(float.class);
                                            counter ++;

                                        }
                                    }

                                    float durchschnitt = sum/counter;

                                    Log.i("durchschnitt" , " " + sum + " "+ counter + " " +durchschnitt);

                                    e.setAverage(durchschnitt);

                                    Log.i("essen wert: " , " " + e.getAverage());

                                }

                                @Override
                                public void onCancelled(@NonNull DatabaseError databaseError) {

                                }
                            });

                            testessen.add(e);

                        }


                        holder.essenName.setText(testessen.get(position).getName());
                        holder.essenPreis.setText(testessen.get(position).getPreis());

                        float test = (float) testessen.get(position).getAverage();
                        Log.i("test :" , " "+ test);
                        holder.test.setText(testessen.get(position).getAverage());

                    }

                    @Override
                    public void onCancelled(@NonNull DatabaseError databaseError) {

                    }
                });
            }
        };

        recyclerView_aktuelle_Bewertungen.setLayoutManager(new LinearLayoutManager(getContext()));
        recyclerView_aktuelle_Bewertungen.setAdapter(adapter);
        recyclerView_aktuelle_Bewertungen.hasFixedSize();
        adapter.startListening();
    }



    public  class EssenViewHolder extends RecyclerView.ViewHolder{

        TextView essenName, essenPreis, test;

        public EssenViewHolder (@NonNull View itemView ) {
            super(itemView);

            essenName = itemView.findViewById(R.id.reihen_name);
            essenPreis = itemView.findViewById(R.id.reihen_preis);

            test = itemView.findViewById(R.id.reihen_test);


        }


    }


}

the output from the average calculation is: for the food test1:

I/durchschnitt: 12.5 4 3.125 -> 12.5/4 = 3.125

For test2:

I/durchschnitt: 12.5 4 3.125 -> 12.5/4 = 3.125

For test3:

I/durchschnitt: 2.5 1 2.5 -> 2.5/1 = 2.5

entries in DB: Test1: 3 + 3+ 4 +2.5 = 12.5 test 2: 3.5+3.5+3+2.5= 12.5 test 3: 2.5 = 2.5 the calculation seems to work just fine...

Here is my essen class:

    String name,Preis;
    float average;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPreis() {
        return Preis;
    }

    public void setPreis(String preis) {
        Preis = preis;
    }

    public float getAverage() {
        return average;
    }

    public void setAverage(float average) {
        this.average = average;
    }


    public essen (String name, String Preis, float average){
        this.name = name;
        this.Preis = Preis;
        this.average = average;
    }

    public essen(){}

}

Here is my XML layout File, which is displayed in an recycler view (i deleted all unnecessary Views, therefore the constrains might look a bit strange):

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:layout_margin="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">



            <TextView
                android:id="@+id/reihen_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@font/roboto"
                android:text="Name"
                android:textColor="@color/rot"
                android:textSize="17sp"
                android:textStyle="bold"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.064"
                app:layout_constraintStart_toEndOf="@+id/reihen_bilder"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.149" />

            <TextView
                android:id="@+id/reihen_preis"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@font/roboto"
                android:text="Preis"
                android:textColor="@color/black"
                android:textSize="17sp"
                android:textStyle="normal"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.723"
                app:layout_constraintStart_toEndOf="@+id/reihen_bilder"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.507" />



            <TextView
                android:id="@+id/reihen_test"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="TextView"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.552"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.14" />


        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

And here is a Screenshot from my Firebase Database:

enter image description here

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 1
    The code looks OK at first glance, but it's hard to play human debugger for this amount of code. If you put a breakpoint in the inner `onDataChange` and run the code in a debugger, does it get to the breakpoint? If so, when you step through the code from there on, does each line do wht you expect it to do? Is the `sum` incrementing as expected? If so, does `durchschnitt` get the right value? – Frank van Puffelen Jun 14 '20 at 14:06
  • Yes I displayed every value (sum, counter, and average) inside the loop on the console (bove Log.i.... are in the code above) there every value is correct according to the entries in the database – Flores_3011 Jun 14 '20 at 14:55
  • But somehow I can't set the average I calculate there to the essen (food) object – Flores_3011 Jun 14 '20 at 14:57
  • Although the other values (name and price) are set the same way and for these values everything works just fine – Flores_3011 Jun 14 '20 at 14:57
  • So it sounds like it's the `e.setAverage(durchschnitt);` that is causing the problem then. If you set a hard-coded string there (`e.setAverage("test");`) does it show up? If you move that statement outside of the data loading, does it show up? – Frank van Puffelen Jun 14 '20 at 15:19
  • I tryed to hardcode an value for the average, but its still the same Problem: within th for-loop i can access the food-objekt and when i display the average in the console i see the hard coded value, but outside the loop when i try to access the average through the food-objekt from the ArrayList the value of average is display as 0.0 in the console is it possible to share the project via github with you ? – Flores_3011 Jun 14 '20 at 16:03
  • i just tried to set an hardcoded average outside the "big" for-loop [ for (DataSnapshot ds : dataSnapshot.getChildren()){.....}] like this: ``` for (essen e: testessen){ e.average = (float) 3.5; } ``` then the hard coded value is shown in the console... But I have no access to the callcullated average from above to set it this way... – Flores_3011 Jun 14 '20 at 16:12
  • Interesting... and the output from `Log.i("durchschnitt" , " " + sum + " "+ counter + " " +durchschnitt)` shows up in your logcat? In general, it's a good idea to include relevant logging output in your question, so we can compare the output with the code that generates it (since we won't be able to see your UI). – Frank van Puffelen Jun 14 '20 at 18:49
  • the output from the average callculations is: for the food test1: I/durchschnitt: 12.5 4 3.125 -> 12.5/4 = 3.125 for test2: I/durchschnitt: 12.5 4 3.125 -> 12.5/4 = 3.125 for test3: I/durchschnitt: 2.5 1 2.5 -> 2.5/1 = 2.5 entires in DB: Test1: 3 + 3+ 4 +2.5 = 12.5 test 2: 3.5+3.5+3+2.5= 12.5 test 3: 2.5 = 2.5 the calcullation seems to work just fine... I wonder why I can't set the average form within the "big" for loop, but outside the loop the average can be set as i wrote above – Flores_3011 Jun 14 '20 at 19:01
  • I think i know what my problem is... I think it has something to do with the asynchronous calls to the Database. Similar to this question: https://stackoverflow.com/questions/40602460/firebase-android-adding-retrieved-values-from-firebase-to-arraylist-returns-nul/40613800#40613800 But I can´t figure out how to change my code to get it to wait until the data is loaded... – Flores_3011 Jun 16 '20 at 18:08

1 Answers1

0

I now see that you're setting the average to the views outside of the onDataChange that calculates the data. That won't work since by the time your holder.test.setText(testessen.get(position).getAverage()) runs, the e.setAverage(durchschnitt) hasn't run yet.

Any code that needs the data from the database needs to be inside the onDataChange for that data, or be called from there.

The simplest way to do this is to just show the average from inside the nested onDataChange:

final ArrayList<essen> testessen =new ArrayList<>();

essenRef.addValueEventListener(new ValueEventListener() {

    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

        for (DataSnapshot ds : dataSnapshot.getChildren()){

            final essen e = new essen();
            e.name  = ds.child("name").getValue(String.class);
            e.Preis = ds.child("Preis").getValue(String.class);

            bewertungenRef.addListenerForSingleValue(new ValueEventListener() {
                @Override
                public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                    float sum = (float) 0.0;
                    int counter = 0;


                    for (DataSnapshot ds1 : dataSnapshot.getChildren()){
                        if (ds1.child("eid").getValue(String.class).equals(e.name)){

                            sum = sum + ds1.child("wert").getValue(float.class);
                            counter ++;

                        }
                    }

                    float durchschnitt = sum/counter;

                    Log.i("durchschnitt" , " " + sum + " "+ counter + " " +durchschnitt);

                    e.setAverage(durchschnitt);

                    Log.i("essen wert: " , " " + e.getAverage());

                    holder.essenName.setText(testessen.get(position).getName());
                    holder.essenPreis.setText(testessen.get(position).getPreis());

                    float test = (float) testessen.get(position).getAverage();
                    Log.i("test :" , " "+ test);
                    holder.test.setText(testessen.get(position).getAverage());
                }

                @Override
                public void onCancelled(@NonNull DatabaseError databaseError) {
                    throw databaseError.toException(); // don't ignore errors
                }
            });

            testessen.add(e);
        }
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {
        throw databaseError.toException(); // don't ignore errors
    }
});

In the above you're loading all data from bewertungenRef, while you only need a subset of these items. You're also reloading that data for every iteration, which is quite wasteful (but should work).

Depending on how much data you have, you might want to consider loading just the child nodes from bewertungenRef that are needed. This would mean you have multiple listeners, and this is a good moment to also then only update the average in the UI once all data is loaded.

That'd look something like this (untested, so the code may contain small problems):

final ArrayList<essen> testessen =new ArrayList<>();

essenRef.addValueEventListener(new ValueEventListener() {

    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        float sum = (float) 0.0;
        int counter = 0;

        // these variables track how many items we need to load, and how many we've already loaded
        long essenCount = dataSnapshot.getChildrenCount();
        long loadedCount = 0;

        for (DataSnapshot ds : dataSnapshot.getChildren()){
            final essen e = new essen();
            e.name  = ds.child("name").getValue(String.class);
            e.Preis = ds.child("Preis").getValue(String.class);

            bewertungenRef.orderByChild("eid").equalTo(e.name).addListenerForSingleValue(new ValueEventListener() {
                @Override
                public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                    for (DataSnapshot ds1 : dataSnapshot.getChildren()){
                        sum = sum + ds1.child("wert").getValue(float.class);
                        counter++;
                    }

                    if (loadedCount++ == essenCont) {
                        float durchschnitt = sum/counter;

                        e.setAverage(durchschnitt);

                        Log.i("essen wert: " , " " + e.getAverage());

                        holder.essenName.setText(testessen.get(position).getName());
                        holder.essenPreis.setText(testessen.get(position).getPreis());

                        holder.test.setText(testessen.get(position).getAverage());
                    }
                }

                @Override
                public void onCancelled(@NonNull DatabaseError databaseError) {
                    throw databaseError.toException(); // don't ignore errors
                }
            });

            testessen.add(e);
        }
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {
        throw databaseError.toException(); // don't ignore errors
    }
});

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thank you so much!! Your first solution is working wonderful!!! I've tried the second one as well, but there my Viewholders don't get populated with data, they just show the XML 'default' layout... But your first solution should be enough for my needs :P – Flores_3011 Jun 17 '20 at 10:36