I am trying to get a list of items from my firebase database...but I have a problem obtaining them. My code looks more or less like this:

List<Items> itemsList;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_work_items_report);

    itemsList = GetItems();
}

and the method that should return my items looks like:

private ArrayList<Items> GetItems(){
    DatabaseReference database = FirebaseDatabase.getInstance().getReference();
    DatabaseReference ref = database.child("items");
    final ArrayList<Items> itemsRez = new ArrayList<Items>();
    Query itemsQuery = ref.orderByChild("type");

    itemsQuery.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            if (dataSnapshot.exists()) {
                for (DataSnapshot singleSnapshot : dataSnapshot.getChildren()) {
                    Items item = singleSnapshot.getValue(Items.class);
                    itemsRez.add(item);
                }
            }
        }
        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    });
    return itemsRez;
}

The GetItems() always returns me a null list, which kinda makes sense, as there is nothing to fire the onDataChange event before I return this list, so my question is...how can I make this method to return the list of items from DB?


Solution 1:

When you call addListenerForSingleValueEvent() the Firebase client starts loading the data from the server, which may take some time. To prevent blocking your app (which would lead to an Application Not Responding dialog), it loads the data in a separate thread. So while the data is loading, your main thread goes on and returns the current state of itemsRez, which is an empty list.

It's easiest to see this if you add a few logging statements to your code:

private ArrayList<Items> GetItems(){
    DatabaseReference database = FirebaseDatabase.getInstance().getReference();
    DatabaseReference ref = database.child("items");
    final ArrayList<Items> itemsRez = new ArrayList<Items>();
    Query itemsQuery = ref.orderByChild("type");

    System.out.println("Attaching listener");
    itemsQuery.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            if (dataSnapshot.exists()) {
                for (DataSnapshot singleSnapshot : dataSnapshot.getChildren()) {
                    Items item = singleSnapshot.getValue(Items.class);
                    itemsRez.add(item);
                }
            }
            System.out.println("Received "+itemsRez.size()+" items");
        }
        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    });
    System.out.println("Returning "+itemsRez.size()+" items");
    return itemsRez;
}

Contrary to what you likely expect, this will print the logging in this order:

Attaching listener

Returning 0 items

Received items

One common solution to your problem is to reframe the goal of you code. Instead of writing "first get the items, then do abc with them", write the code as "start getting the items. Then when they come in, do abc with them". In code that means you move the code that needs itemsRec into the onDataChange method, where it will be invoked at the right moment: when the items have loaded.

Also see:

  • Setting Singleton property value in Firebase Listener
  • Firebase/Android: Adding retrieved values from Firebase to arraylist returns null pointer exception
  • Wait Firebase async retrive data in android
  • this blog post from Doug Stevenson