1

My database structure is:

{
"Received":{
  "Gokul":{
     "5-2021":{"amount":"5000"},
     "6-2021":{"amount":"8000"}
  }
},
"Remaining":{
  "Gokul":{
     "5-2021":{"rem_Amount":"3000"},
     "6-2021":{"rem_Amount":"4000"}
  }
 }
}

In this I need to add my contribution amounts 5000+8000 = 13000 and remaining amount 3000+4000=7000 and need to subtract remaining from contribution and to set it on a Text View. In this I achieved addition operations but can't able to do subtraction. My code is,

 reference = FirebaseDatabase.getInstance().getReference("Received").child("Gokul");
    reference.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            for (DataSnapshot snap : snapshot.getChildren()) {
                String value = snap.child("amount").getValue(String.class);
                tot = tot + Integer.parseInt(value);
            }
            amount1.setText(String.valueOf(tot));
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
            Toast.makeText(DetailsActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
        }
    });

reference = FirebaseDatabase.getInstance().getReference("Remaining").child("Gokul");
    reference.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            for (DataSnapshot snap : snapshot.getChildren()) {
                String value = snap.child("rem_Amount").getValue(String.class);
                rem = rem + Integer.parseInt(value);
            }
            amount2.setText(String.valueOf(rem));
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
            Toast.makeText(DetailsActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
        }
    });

avl = tot - rem;
amount3.setText(String.valueOf(avl));

In this when I try to get tot and rem values it gives me 0. Please assist me.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Gokul
  • 53
  • 6

2 Answers2

2

Data is loaded from Firebase asynchronously. While this data is being loaded, your main code continues to execute. This means that in your code the avl = tot - rem runs before any of the onDataChange methods has run, so tot and rem are still 0.

For this reason, any code that needs the data from the database needs to be inside the onDataChange, or be called from there. Since you have two calls to load data, you'll typically want to nest those calls, and the put the calculation into the inner onDataChange, like this:

reference = FirebaseDatabase.getInstance().getReference("Received").child("Gokul");
reference.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        for (DataSnapshot snap : snapshot.getChildren()) {
            String value = snap.child("amount").getValue(String.class);
            tot = tot + Integer.parseInt(value);
        }
        amount1.setText(String.valueOf(tot));

        reference = FirebaseDatabase.getInstance().getReference("Remaining").child("Gokul");
        reference.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                for (DataSnapshot snap : snapshot.getChildren()) {
                    String value = snap.child("rem_Amount").getValue(String.class);
                    rem = rem + Integer.parseInt(value);
                }
                amount2.setText(String.valueOf(rem));

                avl = tot - rem;
                amount3.setText(String.valueOf(avl));
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {
                Toast.makeText(DetailsActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Toast.makeText(DetailsActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
    }
});

I recommend reading some more on this asynchronous loading, as it's a quite common stumbling block:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Pretty sure nesting the reference and listener for `Remaining/Gokul` here is going to lead to a new listener being added each time `Received/Gokul` or its children get an update as there is no clean-up taking place. – samthecodingman May 23 '21 at 15:43
  • Fair point, although I don't think OP was stuck with that just yet. ;-) I changed it to use `addListenerForSingleValueEvent`. – Frank van Puffelen May 23 '21 at 15:58
2

When working with Firebase, you need to remember that your code is asynchronous.

Integer tot = 0;
Integer rem = 0;
Integer avl = 0;
DatabaseReference reference;

reference = FirebaseDatabase.getInstance().getReference("Received").child("Gokul");
reference.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) { }

    @Override
    public void onCancelled(@NonNull DatabaseError error) { }
});

reference = FirebaseDatabase.getInstance().getReference("Remaining").child("Gokul");
reference.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) { }

    @Override
    public void onCancelled(@NonNull DatabaseError error) { }
});

avl = tot - rem; // this line runs before any of onDataChange/onCancelled handlers above
amount3.setText(String.valueOf(avl));

Next, don't reuse reference like you have. Store them as separate variables so you don't confuse yourself or anyone else who works with that code.

DatabaseReference receivedGokulRef = FirebaseDatabase.getInstance().getReference("Received").child("Gokul");
DatabaseReference remainingGokulRef = FirebaseDatabase.getInstance().getReference("Remaining").child("Gokul");

Next, you need to make sure that both receivedGokulRef and remainingGokulRef get their data first before calculating the balance.

If you are expecting this avl value to get realtime updates, inside of the listeners you add using the Query#addValueEventListener() method, update their respective amount1 and amount2 values and then call a refresh/recalculate method for amount3.

amount3.setText("Loading...");

DatabaseReference receivedGokulRef = FirebaseDatabase.getInstance().getReference("Received").child("Gokul");
receivedGokulRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        Integer sum = 0;
        for (DataSnapshot snap : snapshot.getChildren()) {
            Integer value = snap.child("amount").getValue(Integer.class);
            sum += value;
        }
        amount1.setText(String.valueOf(sum));
        refreshAvaliableBalance(); // <-- trigger calculation
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Toast.makeText(DetailsActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
    }
});

DatabaseReference remainingGokulRef = FirebaseDatabase.getInstance().getReference("Remaining").child("Gokul");
remainingGokulRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        Integer sum = 0;
        for (DataSnapshot snap : snapshot.getChildren()) {
            Integer value = snap.child("amount").getValue(Integer.class);
            sum += value;
        }
        amount2.setText(String.valueOf(sum));
        refreshAvaliableBalance(); // <-- trigger calculation
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Toast.makeText(DetailsActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
    }
});

/* as a method of DetailsActivity */
private void refreshAvaliableBalance() {
    String amount1Text = amount1.getText();
    String amount2Text = amount2.getText();
    
    if (amount1Text == "" || amount2Text == "") {
        // still loading
        amount3.setText("Loading...");
        return;
    }
    
    Integer receivedSum = Integer.parseInt(amount1Text);
    Integer remainingSum = Integer.parseInt(amount2Text);
    
    Integer available = receivedSum - remainingSum;
    
    amount3.setText(String.valueOf(available));
}

If you are only retrieving the balance once you could use Query#get() which returns a Task containing your data. You can then combine this with the Tasks utility methods, you can chain actions together.

For receivedGokulRef, get its value and sum it together. For remainingGokulRef, get its value and sum it together. Once we have both sums, calculate the difference.

Task<Integer> receivedGokulSumTask = receivedGokulRef.get()
    .onSuccessTask(new SuccessContinuation<DataSnapshot, Integer>() {
        @NonNull
        @Override
        public Task<Integer> then(DataSnapshot querySnapshot) {
            Integer sum = 0;
            for (DataSnapshot childSnap : querySnapshot.getChildren()) {
                Integer value = snap.child("amount").getValue(Integer.class);
                sum += value;
            }
            return sum;
        }
    });

Task<Integer> remainingGokulSumTask = remainingGokulRef.get()
    .onSuccessTask(new SuccessContinuation<DataSnapshot, Integer>() {
        @NonNull
        @Override
        public Task<Integer> then(DataSnapshot querySnapshot) {
            Integer sum = 0;
            for (DataSnapshot childSnap : querySnapshot.getChildren()) {
                Integer value = snap.child("amount").getValue(Integer.class);
                sum += value;
            }
            return sum;
        }
    });

Tasks.whenAllComplete([receivedGokulSumTask, remainingGokulSumTask])
    .addOnSuccessListener(new OnSuccessListener<List<Task<Integer>>>() {
        // Tasks.whenAllComplete never fails, so we can use addOnSuccessListener for simplicity
        @NonNull
        @Override
        public void onSuccess(List<Task<Integer>> sumTasks) {
            Task<Integer> receivedSumTask = sumTasks.get(0);
            Task<Integer> remainingSumTask = sumTasks.get(1);

            if (receivedSumTask.isSuccessful() && remainingSumTask.isSuccessful()) {
                // got data properly

                Integer receivedSum = receivedSumTask.getResult();
                Integer remainingSum = remainingSumTask.getResult();
                Integer available = receivedSum - remainingSum; // throw error when negative?

                amount1.setText(String.valueOf(receivedSum)); // TODO: rename amount1,amount2,amount3
                amount2.setText(String.valueOf(remainingSum));
                amount3.setText(String.valueOf(available));

                return; // done
            }

            // if here, either or both tasks failed.
            Exception receivedSumException = receivedSumTask.getException();
            Exception remainingSumException = remainingSumTask.getException();

            StringBuilder messageBuilder = new StringBuilder("Error(s) fetching data");
            if (receivedSumException !== null) {
                messageBuilder.append("; Error while getting received total sum: " + receivedSumException.getMessage());
            }
            if (remainingSumException !== null) {
                messageBuilder.append("; Error while getting remaining total sum: " + receivedSumException.getMessage());
            }
            Toast.makeText(DetailsActivity.this, messageBuilder.toString(), Toast.LENGTH_SHORT).show();
        }
    })
samthecodingman
  • 23,122
  • 4
  • 30
  • 54