0

I am working on an app for a hotel, which enables hotel management to report and view concerns and issues. I am using Android and Firebase for this app.

Here is the database structure of a reported concern: Concern POJO structure

To minimize data download and optimize speed, I am adding "Active" and "Resolved" nodes in the database, like below:

enter image description here

Now, the hotel wants me to add the function to create an Excel report of concerns closed/resolved within the past month. For this, I will be attaching a Single Value Event Listener on the "resolved" node, get keys of resolved concerns, then for each key, fetch data from "allConcerns" node, store each node's data into an ArrayList of String. After which I will use this JSON to Excel API for Android to create Excel file.

I am able to access keys of resolved concerns with this code:

DatabaseReference resolvedReference = FirebaseDatabase.getInstance().getReference()
            .child(getApplicationContext().getResources().getString(R.string.concerns))
            .child(getApplicationContext().getResources().getString(R.string.resolved));

    final ArrayList<String> keys = new ArrayList<>();

    resolvedReference.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {

            //Getting keys of all resolved concerns in keys arraylist here
            for (DataSnapshot ds : snapshot.getChildren()){
                keys.add(ds.getValue(String.class));
            }

            //Storing JSON data in this arraylist
            final ArrayList<String> data = new ArrayList<>();

            for(int i = 0; i<keys.size() ; ++i){
                String key = keys.get(i);

            //Getting data of each concern here  
            FirebaseDatabase.getInstance().getReference().child(getApplicationContext().getResources().getString(R.string.allConcerns))
                        .child(key).addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull DataSnapshot snapshot) {
                        String type = snapshot.child("type").getValue().toString();
                        Log.i("Type", type);
                        if(type.equals("0")) {
                            SafetyConcernClass s = snapshot.getValue(SafetyConcernClass.class);
                            Log.i("Snapshot of key", s.toString());
                            data.add(s.toString());
                        }
                        else{
                            GembaWalkClass g = snapshot.getValue(GembaWalkClass.class);
                            Log.i("Snapshot of key", g.toString());
                            data.add(g.toString());
                        }
                        Proof proof = snapshot.child("proof").getValue(Proof.class);
                        Log.i("Proof", proof.toString());
                    }

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

                    }
                });
            }

            //Issue I am facing is here
            Log.i("Data size", String.valueOf(data.size()));

        }

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

        }
    });

}

The real issue here is while logging data.size(). Since Firebase is asynchronous, FOR loop ends before data is fetched and entered into the data ArrayList, hence it gives me a size of 0. And since no data is fetched, I can't create an Excel file.

My question is, how can I make sure I am proceeding to log data.size() ONLY after data of respective resolved concerns is stored in the ArrayList?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Manan Tyagi
  • 50
  • 10
  • As soon as you call the second firebase api, you need to suspend that execution until your function returns. One way is to convert the callback based firebase api to Kotlin coroutines using `suspendCoroutine` builder. Please check this link for further reference https://proandroiddev.com/suspending-firebase-realtime-database-with-kotlin-coroutines-76b4651bc0e8 – iCantC Mar 14 '21 at 14:15
  • @iCantC That is a good resource, but unfortunately I am using Java. Would you recommend any other option available in Java? – Manan Tyagi Mar 14 '21 at 14:28

2 Answers2

0

The typical approach is to keep a counter or a countdown latch to track how many of the concern snapshots you've already downloaded. Once the counter reaches keys.size() you know that you're done.

Also see Setting Singleton property value in Firebase Listener

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I did exactly what you said, but the app showed ANR and crashed, as mentioned in your comment on the article you shared. – Manan Tyagi Mar 14 '21 at 16:03
-1

You should write your method

addListenerForSingleValueEvent

using AsyncTask or Kotlin coroutines and in onPostExecute() of AsyncTask, you can proceed to further action.