1

I have a Firebase Database. I want to create a query and put it in a method in a DBUtils class so that I can call the query from different activities in an Android project.

For Example, something like :

ArrayList<String> aStringArrayList = DBUtils.GetAllJobsOnADate(Date date);
.
.
.
// use aStringArrayList to populate a listview.

AND

 public  static ArrayList<String> GetJobsOnADate(Date date) {
                final ArrayList<String> mReturnList = new ArrayList<String>();
                FirebaseFirestore db = FirebaseFirestore.getInstance();
                db.collection("jobs")
                        .whereEqualTo("job_date", date)
                        .get()
                        .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
                            @Override
                            public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
                                for (QueryDocumentSnapshot queryDocumentSnapshot : queryDocumentSnapshots) {
                                     mReturnList.add(queryDocumentSnapshot.get("job_title"));
                                                                                                                }
                            }
                        })
                        .addOnFailureListener(new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {
                                // failure code 
                            }
                        });
                return mReturnList;
            }

When I debug the app, I can see that the get() does indeed return a list of QuerySnapshotDocument (can see the breakpoint being hit in onSuccessListner code) but aStringArray always is empty.

Tried using AsyncTask (onPostExecute). Doesn't work either.

Of course, works fine if the code is in a function in the same activity. But then that is a lot of redundant code spread over many activities.

Any idea?

MethodToChaos
  • 335
  • 2
  • 17

2 Answers2

1

Here is a Java implementation of the above answer.

static GetJobsOnADate(Date date, MyCallbackInterface callback) {
            final ArrayList<String> mReturnList = new ArrayList<String>();
            FirebaseFirestore db = FirebaseFirestore.getInstance();
            db.collection("jobs")
                    .whereEqualTo("job_date", date)
                    .get()
                    .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
                        @Override
                        public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
                            for (QueryDocumentSnapshot queryDocumentSnapshot : queryDocumentSnapshots) {
                                 mReturnList.add(queryDocumentSnapshot.get("job_title"));
                                }
                            callback.OnDataReceived(mReturnList);
                        }
                    })
                    .addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            // failure code 
                        }
                    });

        }

And the callback interface:

interface MyCallbackInterface{
    void onDataReceived(ArrayList<String> data);

}

codeman
  • 144
  • 7
0

The only reason is because you are actually returning an empty list, the method is synchronous and Firebase calls to the server are asynchronous so return mReturnList; is called before mReturnList.add(queryDocumentSnapshot.get("job_title"));

since I didn't write Java for a long time, I would suggest you some ways to achieve that but in Kotlin. I'll try making it similar to Java syntax.

so, for that you should use interfaces to communicate with that class.

your interface would look like this:

interface Callback {
   fun onListLoaded(list : ArrayList<String>)
}

then in your class which need that list should implement that interface:

class MyActivity() : Activity(), Callback /*<- your interface*/ {
  ...
  //You implement the method which is signed in your interface.
  override fun onListLoaded(list : ArrayList<String>) {
    //here you can use the list
    list.forEach { ... }
  }

}

then you should pass an instance of your activity that implements your Callback interface to that class which communicates with firebase:

class FirebaseInteractor() {

    private var listener: Callback? = null //instance of your interface

    fun setListener(listener : Callback) {
        this.listener = listener
    }  

    fun getJobsOnADate(Date date) {
        ...
    }
}

then when your data is loaded, in your onSuccessListener you call your interface that way:

fun getJobsOnADate(Date date) {
            val db = FirebaseFirestore.getInstance()
            db.collection("jobs")
                    .whereEqualTo("job_date", date)
                    .get()
                    .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
                        @Override
                        public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
                            val list : ArrayList<String> = ArrayList()
                            for (queryDocumentSnapshot in queryDocumentSnapshots) {
                                 list .add(queryDocumentSnapshot.get("job_title"))                                                                        
                            }

                            //Here goes the magic:
                            listener.onListLoaded(list)
                        }
                    })
                    .addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            // failure code 
                        }
                    })
        }

and here is how you set your activity which implements Callback interface as a listener (inside your activity):

val firebaseInteractor = FirebaseInterractor()
firebaseInterractor.setListener(this)
firebaseInterractor.getJobsOnADate(date)

when onSuccessListener of firebase is called and all data is parsed, onListLoaded in your activity will be called with the list sent by the interactor.

Tamim Attafi
  • 2,253
  • 2
  • 17
  • 34
  • 1
    WoW. That is beautifully complex and simple in an event-driven programming world. I should have thought about event-driven programming, but I lack the programming experience. Thank you very much, Tamim. I shall try that out and will get back to ticking your answer by tomorrow. And yes, I am seriously thinking about shifting to Kotlin. – MethodToChaos Sep 25 '19 at 13:03
  • Well, worked like a charm. Thanks again. I actually ended up passing the callback as a parameter (per Wambada's suggestion) and giving the parameter to the callback itself an object array instead of ArrayList, just to make it more generic. – MethodToChaos Sep 29 '19 at 07:59
  • It's not the best practice to pass a callback with other parameters but It works and if it's comfortable to so good luck! – Tamim Attafi Sep 30 '19 at 11:07