1

I am trying to access some data from a Firestore collection. I am then trying to return that array of transactions. However I am unable to do this because of the OnComplete method.

public ArrayList<Transaction> getTransactions(){

    //initializing firebase auth object
    firebaseAuth = FirebaseAuth.getInstance();
    FirebaseUser currentUser = firebaseAuth.getCurrentUser();
    //Establishing database connection
    db = FirebaseFirestore.getInstance();

    DocumentReference docRef = db.collection("users").document(currentUser.getEmail());

    //this is the array I am trying to return.
    ArrayList<Transaction> trans = new ArrayList<>();

    docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
        @Override
        public void onComplete(@NonNull Task<DocumentSnapshot> task) {

            if (task.isSuccessful()) {
                DocumentSnapshot document = task.getResult();
                if (document.exists()) {
                    Log.d(TAG, "DocumentSnapshot data: " + document.getData());

                    //update the array with the data received.
                    trans = (ArrayList<Transaction>) document.getData().get("transactions");


                } else {
                    Log.d(TAG, "No such document");
                }
            } else {
                Log.d(TAG, "get failed with ", task.getException());
            }
        }
    });

    return trans;
}

The returned output is an empty list because the onComplete method is being triggered after the getTransactions() returns.

James Gordon
  • 21
  • 1
  • 3
  • 1
    You can't return data that hasn't loaded yet. And there's no way to make Android wait for the data to load before executing the `return trans` statement, as that would block the user from interacting with your app. Any code that requires the data, needs to be within `onComplete` or be called from there. See for example https://stackoverflow.com/a/50277198 or https://stackoverflow.com/a/50435519 – Frank van Puffelen Mar 24 '19 at 00:03
  • 1
    @FrankvanPuffelen Hey thanks for your reply. I took a look at the second link you sent. I am not sure how to implement your solution with my code. I'd appreciate if you could help me out. – James Gordon Mar 24 '19 at 00:20
  • Both answers show how to implement a custom interface for your callback. Fred's answer also shows an implementation of that, tailored to your case. If you make it work from the linked answers, you can also embed the code that needs the data into the `onComplete` method. – Frank van Puffelen Mar 24 '19 at 01:17

1 Answers1

2

As stated in the comments, it's not possible to return data that is not loaded yet, so one way you can do this is by propagating the callback. Here's a simple example:

interface OnTransactionListReceivedListener {
     void onTransactionListReceived(List<Transaction> result);
}

public void getTransactions(OnTransactionListReceivedListener listener){

  //initializing firebase auth object
  firebaseAuth = FirebaseAuth.getInstance();
  FirebaseUser currentUser = firebaseAuth.getCurrentUser();
  //Establishing database connection
  db = FirebaseFirestore.getInstance();

  DocumentReference docRef = db.collection("users").document(currentUser.getEmail());

  docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
      @Override
      public void onComplete(@NonNull Task<DocumentSnapshot> task) {

          if (task.isSuccessful()) {
              DocumentSnapshot document = task.getResult();
              if (document.exists()) {
                listener.onTransactionListReceived((ArrayList<Transaction>) document.getData().get("transactions"));
              } else {
                  Log.d(TAG, "No such document");
              }
          } else {
              Log.d(TAG, "get failed with ", task.getException());
          }
      }
  });;
}

We define an interface which has a single method that gets called once the list is received. This is our callback. The caller will received this list once the list is returned by firebase. You can call the method for example like:

getTransactions(new OnTransactionListReceivedListener() {
  public void onTransactionListReceived(List<Transaction> result) {
      // Use the list here
  }
});

Of course you might need to propagate the callback even further and this can easily degenerate into what's called callback hell. This is why some people prefer an approach that seems synchronous, but it's in fact asynchronous, i.e., reactive streams or futures.

Fred
  • 16,367
  • 6
  • 50
  • 65