getContactsFromFirebase() method return an empty list

后端 未结 1 2148
梦谈多话
梦谈多话 2020-11-21 04:38
public List getContactsFromFirebase(){
    FirebaseDatabase.getInstance().getReference().child(\"Users\")
            .addListenerForSingleValueEvent(n         


        
1条回答
  •  日久生厌
    2020-11-21 04:56

    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 value);
    }
    

    Kotlin:

    interface UserListCallback {
      fun onCallback(value:List)
    }
    

    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) {
        System.out.println("Loaded "+users.size()+" contacts")
      }
    });
    

    Kotlin:

    getContactsFromFirebase(object:UserListCallback() {
      fun onCallback(users:List) {
        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:

    • this blog post from Doug
    • Setting Singleton property value in Firebase Listener (where I explained how in some cases you can get synchronous data loading, but usually can't)
    • return an object Android (the first time I used the log statements to explain what's going on)
    • Is it possible to synchronously load data from Firebase?
    • https://stackoverflow.com/a/38188683 (where Doug shows a cool-but-complex way of using the Task API with Firebase Database)
    • How to return DataSnapshot value as a result of a method? (from where I borrowed some of the callback syntax)

    0 讨论(0)
提交回复
热议问题