0

I want to be store the number of children in a Firebase Database to a variable in Android Studio. I am trying to get the total all the nodes under 2 different children in Firebase but it returns java.lang.ArithmeticException: divide by zero. Any help will be greatly appreciated.

private long absent, present, total, attendanceRecord;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_student_stats);

    statsResult = (TextView) findViewById(R.id.statsResult);

    ValueEventListener eventListener = new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            for (DataSnapshot ds : dataSnapshot.getChildren()) {
                String name = ds.getKey();


                String selectedClass;
                Bundle extras = getIntent().getExtras();
                if (extras == null) {
                    selectedClass = null;
                } else {
                    selectedClass = extras.getString("classSelected");
                }
                absentNumber = FirebaseDatabase.getInstance().getReference().child("Users").child(user.getUid()).child("Classes").child(selectedClass).child("AbsentStudents").child(name);
                presentNumber = FirebaseDatabase.getInstance().getReference().child("Users").child(user.getUid()).child("Classes").child(selectedClass).child("PresentStudents").child(name);


                presentNumber.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for (DataSnapshot snap: dataSnapshot.getChildren()) {
                            present = snap.getChildrenCount() ;
                        }
                    }
                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });

                absentNumber.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        for (DataSnapshot snap: dataSnapshot.getChildren()) {
                            absent = snap.getChildrenCount() ;
                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });

                total = present + absent;
                attendanceRecord = present / total;

                allStudentsList.add(name + "\t" + attendanceRecord + "%");

            }
Sachin Rajput
  • 4,326
  • 2
  • 18
  • 29

1 Answers1

0

The problem is that data is loaded from Firebase asynchronously, and when you run the division the data hasn't loaded yet (so total is 0). To understand how asynchronous loading works, let's add some simple logging to your code:

System.out.println("Before attaching listener");
presentNumber.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        System.out.println("Got data");
    }
    @Override
    public void onCancelled(DatabaseError databaseError) {
        throw databaseError.toException(); // don't ignore errors
    }
});
System.out.println("After attaching listener");

When you run this code, you'll get:

Before attaching listener

After attaching listener

Got data

That is probably not the order you expected. Because it takes some time to get the data from the Firebase servers, your main code continues (and runs the "After" log). Only once the data is available from Firebase, does your onDataChange get called.

Knowing this, I hope the error makes more sense: by the time you run attendanceRecord = present / total, the total will still be 0 because the data hasn't been loaded yet.

The solution to deal with asynchronous loading is to reframe your problem. Right now your code says: "first load A, then load B, then do something with A and B". Instead I find it helpful to frame it as: "first start loading A and B. Once A and B are loaded, do something with them." In your code that would translate to adding an extra function:

public void checkAttendanceRecord(long present, long absent) {
  if (present >= 0 && absent >= 0) {
    total = present + absent;
    attendanceRecord = present / total;

    allStudentsList.add(name + "\t" + attendanceRecord + "%");
  }
}

And then

long present = -1;
long absent = -1;

presentNumber.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot snap: dataSnapshot.getChildren()) {
            present = snap.getChildrenCount() ;
        }
        checkAttendanceRecord(present, absent);
    }
    @Override
    public void onCancelled(DatabaseError databaseError) {
        throw databaseError.toException(); // don't ignore errors
    }
});

absentNumber.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot snap: dataSnapshot.getChildren()) {
            absent = snap.getChildrenCount() ;
        }
        checkAttendanceRecord(present, absent);
    }

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

Note that I'm not really sure what the loops in onDataChange are for, so I left them as is and focused only on the asynchronous nature of the code.

To learn more about asynchronous data loading, have a look at:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I added this but it still throws the error java.lang.ArithmeticException: divide by zero. – WesMantooth Apr 09 '18 at 19:08
  • If you made the changes I showed, it sounds right now like you're reading from something that doesn't have child nodes. It's hard to troubleshoot what's going on in your code. There is simply a lot of code, and I can't see what data you're invoking it on. To make it easier to help, step through your code. Set a breakpoint in the `onDataChange` methods. Do they get invoked? Are they setting the value to what you expect? If not, what do you see? That will allow you to [create a minimal, complete, verifiable example](http://stackoverflow.com/help/mcve), which makes it more likely we help. – Frank van Puffelen Apr 09 '18 at 19:51
  • Is it possible to keep the two addListenerForSingleValueEvents inside the onDataChange that I had at the beginning because that is needed to get the DatabaseReference for both the presentNumber.addListenerForSingleValueEvent and absentNumber.addListenerForSingleValueEvent? – WesMantooth Apr 11 '18 at 11:22
  • Yes, that should make no difference. – Frank van Puffelen Apr 11 '18 at 14:54