0

Currently Running into an issue where I'm fetching data from firebase. I know it is because Firebase is asynchronous, so when I make my firebase call, it is executed on its own thread, and the current thread that it was called on continues to execute. I'm populating a list of objects with the data from firebase and I return that list of objects. The thing is, the list is always returning null because the execution of the firebase code isn't completed in time.

I created some asynchronous code that fetches from SQLite db that works fine, but this approach does not seem to work with firebase (I believe its due to firebases API being asynchronous) Here is my method to return a list of objects from firebase.

/** Method to get activity data from firebase.
 * @param userInput the user query to select the data
 * @return a list of activity models based on the query
 * */
public List<ActivityModel> retrieveActivityData(String userInput) {
    Log.d(TAG, "retrieveActivityData: starts");
    List<ActivityModel> models = new ArrayList<ActivityModel>();
    // execute the query in firebase
    CollectionReference activfitCollection = db.collection("activity");
    activfitCollection.orderBy("isoTimestamp")
            .startAt(userInput)
            .endAt(DateHelper.getDayEndingDate(userInput))
            .get()
            .addOnCompleteListener(task -> {
                if (task.isSuccessful()) {
                    Log.d(TAG, "onComplete: Getting data successful!");
                    // check to see if it exists
                    if (!task.getResult().isEmpty()) {
                        for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
                            Log.d(TAG, "retrieveActivityData: document = " + documentSnapshot.getId());
                            // cast the document to the activity model
                            Log.d(TAG, "retrieveActivityData: document data " + documentSnapshot.getData());
                            ActivityModel model = mapToActivityModel(documentSnapshot);
                            models.add(model);
                            Log.d(TAG, "retrieveActivityData: array size" + models.size());
                        }
                    }

                } else {
                    Log.e(TAG, "onComplete: Error getting documents: ", task.getException());
                }
            });
    Log.d(TAG, "retrieveActivityData: array size outside " + models.size());
    return models;
}
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
AfternoonTiger
  • 357
  • 1
  • 4
  • 10

3 Answers3

2

Option - 1: You can use LiveData to achieve this. Post value to LiveData when operation complete and observe that inside your activity or fragment

MutableLiveData<List<ActivityModel>> listMutableLiveData = new MutableLiveData<>();

public MutableLiveData<List<ActivityModel>> retrieveActivityData(String userInput) {

    List<ActivityModel> models = new ArrayList<ActivityModel>();

    ....
        if (task.isSuccessful()) {
            for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
                ....
                models.add(model);
            }

            //Post value to live data from here
            listMutableLiveData.postValue(models);
        }

    ....

    return listMutableLiveData;
}

And then observe like this

retrieveActivityData(userInput).observe(this, new Observer<List<ActivityModel>>() {
    @Override
    public void onChanged(List<ActivityModel> activityModels) {
        //you can use list here
    }
});

Option - 2: You can use callback function to get result when firebase operation complete.

  • Create an interface for callback
interface FirebaseResultListener {
    void onComplete(List<ActivityModel> activityModels);
}
  • Configure your retrieveActivityData to handle this callback
public void retrieveActivityData(String userInput, FirebaseResultListener callback) {

    List<ActivityModel> models = new ArrayList<ActivityModel>();

    ....
    if (task.isSuccessful()) {
        for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
                ....
            models.add(model);
        }

        //Invoke callback with result from here
        callback.onComplete(models);
    }

    ....
}
  • Implement this interface in your activity or fragment
retrieveActivityData(userInput, new FirebaseResultListener() {
    @Override
    public void onComplete(List<ActivityModel> activityModels) {
        //you can use list here
    }
});
Md. Asaduzzaman
  • 14,963
  • 2
  • 34
  • 46
  • Awesome, thank you for your feedback and responses. Originally, I was using AsyncTask to execute this method on a separate thread. I created my own custom callback method that would should have returned the list of objects from firebase. But I will check these solutions out and see which ones best fits! – AfternoonTiger Feb 12 '20 at 19:12
  • Question, what if I do not want to use the data within the fragment/activity? for example, I'm not updating the updating the UI with the data that is fetched from firebase. I just want to store in memory the data the is fetched from firebase and make sure it waits for that data to load in memory, should I have that specific class implement the callback interface or must it be an activity or fragment that implements the callback interface? – AfternoonTiger Feb 13 '20 at 16:25
  • You can use callback approach from anywher. If this help you then please accept the answer – Md. Asaduzzaman Feb 13 '20 at 17:07
  • @FanonX, Please accept the answer for future users so that they can find it helpful – Md. Asaduzzaman Feb 17 '20 at 05:50
1

Since the data is loaded async you cannot return the data, you should pass callback(Interface) in retrieveActivityData method, and use callback of interface to load the data, check the code bellow

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

        MyFirebaseCallback myFirebaseCallback = new MyFirebaseCallback() {
            @Override
            public void dataLoaded(List<ActivityModel> activityModels) {
                //set the data in recycler view
            }
        };

        retrieveActivityData("myInput",myFirebaseCallback);
    }

    public void retrieveActivityData(String userInput, final MyFirebaseCallback callback) {
        Log.d(TAG, "retrieveActivityData: starts");
        // execute the query in firebase
        CollectionReference activfitCollection = db.collection("activity");
        activfitCollection.orderBy("isoTimestamp")
                .startAt(userInput)
                .endAt(DateHelper.getDayEndingDate(userInput))
                .get()
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        List<ActivityModel> models = new ArrayList<ActivityModel>();
                        Log.d(TAG, "onComplete: Getting data successful!");
                        // check to see if it exists
                        if (!task.getResult().isEmpty()) {
                            for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
                                Log.d(TAG, "retrieveActivityData: document = " + documentSnapshot.getId());
                                // cast the document to the activity model
                                Log.d(TAG, "retrieveActivityData: document data " + documentSnapshot.getData());
                                ActivityModel model = mapToActivityModel(documentSnapshot);
                                models.add(model);
                                Log.d(TAG, "retrieveActivityData: array size" + models.size());
                            }
                        }

                        callback.dataLoaded(models);

                    } else {
                        Log.e(TAG, "onComplete: Error getting documents: ", task.getException());
                    }
                });
        Log.d(TAG, "retrieveActivityData: array size outside " + models.size());
    }

    interface MyFirebaseCallback{
        void dataLoaded(List<ActivityModel> activityModels);
    }
ked
  • 2,426
  • 21
  • 24
0

You have guessed it right! The query is asynchronous so retrieveActivityData() shouldn't return List<ActivityModel> or it would always be null. You would have to use an Event Bus to fire an event as soon as your List is compiled inside onComplete() or use LiveData and observe it.

LiveData

ViewModel

EventBus

Shahood ul Hassan
  • 745
  • 2
  • 9
  • 19