Home » Android » android – getContactsFromFirebase() method return an empty list-Exceptionshub

android – getContactsFromFirebase() method return an empty list-Exceptionshub

Posted by: admin February 26, 2020 Leave a comment

Questions:
public List<String> getContactsFromFirebase(){
    FirebaseDatabase.getInstance().getReference().child("Users")
            .addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
                        Users user = snapshot.getValue(Users.class);
                        assert user != null;
                        String contact_found = user.getPhone_number();
                        mContactsFromFirebase.add(contact_found);
                        Log.i("Test", mContactsFromFirebase.toString());
                    }

                }
                @Override
                public void onCancelled(DatabaseError databaseError) {
                }
            });

    return mContactsFromFirebase;

}

I can’t seem to find the error. In the code above, when I call the log, I get the values from mContactsFromFirebase, but the getContactsFromFirebase() method return an empty list. Could you help me please?

How to&Answers:

Data is loaded from Firebase asynchronously. Since it may take some time to get the data from the server, the main Android code continues and Firebase calls your onDataChange when the data is available.

This means that by the time you return mContactsFromFirebase it is still empty. The easiest way to see this is by placing a few log statements:

System.out.println("Before attaching listener");
FirebaseDatabase.getInstance().getReference().child("Users")
    .addListenerForSingleValueEvent(new ValueEventListener() {
      @Override
      public void onDataChange(DataSnapshot dataSnapshot) {
        System.out.println("In onDataChange");
      }
      @Override
      public void onCancelled(DatabaseError databaseError) {
        throw databaseError.toException(); // don't ignore errors
      }
    });
System.out.println("After attaching listener");

When you run this code, it will print:

Before attaching listener

After attaching listener

In onDataChange

That is probably not the order that you expected the output in. As you can see the line after the callback gets called before onDataChange. That explains why the list you return is empty, or (more correctly) it is empty when you return it and only gets filled later.

There are a few ways of dealing with this asynchronous loading.

The simplest to explain is to put all code that returns the list into the onDataChange method. That means that this code is only execute after the data has been loaded. In its simplest form:

public void onDataChange(DataSnapshot dataSnapshot) {
    for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
        Users user = snapshot.getValue(Users.class);
        assert user != null;
        String contact_found = user.getPhone_number();
        mContactsFromFirebase.add(contact_found);
        System.out.println("Loaded "+mContactsFromFirebase.size()+" contacts");
    }
}

But there are more approaches including using a custom callback (similar to Firebase’s own ValueEventListener):

Java:

public interface UserListCallback {
  void onCallback(List<Users> value);
}

Kotlin:

interface UserListCallback {
  fun onCallback(value:List<Users>)
}

Now you can pass in an implementation of this interface to your getContactsFromFirebase method:

Java:

public void getContactsFromFirebase(final UserListCallback myCallback) {
  databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
      for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
        Users user = snapshot.getValue(Users.class);
        assert user != null;
        String contact_found = user.getPhone_number();
        mContactsFromFirebase.add(contact_found);
        System.out.println("Loaded "+mContactsFromFirebase.size()+" contacts");
      }
      myCallback.onCallback(mContactsFromFirebase);
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
      throw databaseError.toException();
    }
  });
}

Kotlin:

fun getContactsFromFirebase(myCallback:UserListCallback) {
  databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(object:ValueEventListener() {
    fun onDataChange(dataSnapshot:DataSnapshot) {
      for (snapshot in dataSnapshot.getChildren())
      {
        val user = snapshot.getValue(Users::class.java)
        assert(user != null)
        val contact_found = user.getPhone_number()
        mContactsFromFirebase.add(contact_found)
        System.out.println("Loaded " + mContactsFromFirebase.size() + " contacts")
      }
      myCallback.onCallback(mContactsFromFirebase)
    }
    fun onCancelled(databaseError:DatabaseError) {
      throw databaseError.toException()
    }
  })

And then call it like this:

Java:

getContactsFromFirebase(new UserListCallback() {
  @Override
  public void onCallback(List<Users> users) {
    System.out.println("Loaded "+users.size()+" contacts")
  }
});

Kotlin:

getContactsFromFirebase(object:UserListCallback() {
  fun onCallback(users:List<Users>) {
    System.out.println("Loaded " + users.size() + " contacts")
  }
})

It’s not as simple as when data is loaded synchronously, but this has the advantage that it runs without blocking your main thread.

This topic has been discussed a lot before, so I recommend you check out some of these questions too: