2

I found that my ListView is not updated when the app is run because the adapter is set before the Firebase query occurs.

First Question: Is there anyway to set a listener for query completion? This way I can set the adapter there instead. I know some Firebase methods auto-generate onComplete()/onSuccess() listeners.

For debugging purposes I can trigger an update to the ListView by re-entering onResume(). This is where I receive an error message:

The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(16908298, class android.widget.ListView) with Adapter(class android.widget.ArrayAdapter)]

To my understanding this error means I cannot add items to my ArrayList "usernames" inside the onChildAdded() method if I use usernames in my ArrayAdapter.

Second Question: How else can I store the values returned from my Firebase query and use those to ultimately populate a ListView without directly changing its contents from a "background thread"?

My attempt:

public class EditFriendsActivity extends AppCompatActivity {

    protected ArrayList<String> usernames = new ArrayList<>();

    @Override
    protected void onResume() {
        super.onResume();

        Firebase usernameRef = ref.child("users");
        Query queryRef = usernameRef.orderByKey(); 

        queryRef.addChildEventListener(new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
            String username = (String) dataSnapshot.child("username").getValue(); 
            usernames.add(username); 
            Log.v(TAG, usernames + "");
        }
        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
            Log.v(TAG, "Inside ChildChanged");
        }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) {
            Log.v(TAG, "Inside ChildRemoved");
        }

        @Override
        public void onChildMoved(DataSnapshot dataSnapshot, String s) {
            Log.v(TAG, "Inside ChildMoved");
        }

        @Override
        public void onCancelled(FirebaseError firebaseError) {

            Log.e(TAG, firebaseError.getMessage());

            AlertDialog.Builder builder = new AlertDialog.Builder(EditFriendsActivity.this);
            builder.setTitle(R.string.error_title)
            .setMessage(firebaseError.getMessage())
            .setPositiveButton(android.R.string.ok, null);

            AlertDialog dialog = builder.create();
            dialog.show();
            }
        });

        // Array adapter
        ArrayAdapter<String> adapter = new ArrayAdapter<>(
                EditFriendsActivity.this,
                android.R.layout.simple_list_item_multiple_choice, 
                usernames);

        mFriendsList.setAdapter(adapter); 
    }
}  

Updated Code: Fixed typo in listener from marked answer and cleared list each time.

Firebase userRef = ref.child("users");
userRef.addListenerForSingleValueEvent(new ValueEventListener() {

    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        usernames.clear(); // Clear list before updating
        for (DataSnapshot child : dataSnapshot.getChildren()) {
            String username = (String) child.child("username").getValue();
            usernames.add(username);
            Log.v(TAG, usernames + "");
        }        

Follow Up Question Here

Community
  • 1
  • 1
young_souvlaki
  • 1,886
  • 4
  • 24
  • 28
  • I'll write an answer in a bit, but you might want to have a look at https://github.com/firebase/FirebaseUI-Android. This library has a FirebaseListAdapter implementation that handles of this for you. – Frank van Puffelen Sep 10 '15 at 20:54
  • you should probably make a new question and delete the follow up question buddy – Ben Sep 11 '15 at 17:22
  • Also: if theres a typo on my answer, just fix it there. We don't mind fixes to our posts here on StackOverflow. If your solution is substantially different, add it as an answer of your own. – Frank van Puffelen Sep 11 '15 at 22:06
  • Very cool! Did not know I could edit another's post. I appreciate the tips as I am fairly new here and with coding in general. It's really encouraging to have a community like this! – young_souvlaki Sep 12 '15 at 01:42

2 Answers2

3

Is there anyway to set a listener for query completion? This way I can set the adapter there instead. I know some Firebase methods auto-generate onComplete()/onSuccess() listeners.

Firebases synchronizes data from a backend to your application. So unlike a traditional database that has a request/response flow, Firebase queries don't really complete. That's why in the FirebaseUI library we keep an open listener and notify the ListView of any changes.

That said: if you just want to load the data from a location once, you can use addSingleValueEventListener:

queryRef.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        for (DataSnapshot child: snapshot.getChildren()) {
            String username = (String) child.child("username").getValue(); 
            usernames.add(username); 
        }
        ArrayAdapter<String> adapter = new ArrayAdapter<>(
            EditFriendsActivity.this,
            android.R.layout.simple_list_item_multiple_choice, 
            usernames);

        mFriendsList.setAdapter(adapter); 
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {
        Log.e(TAG, firebaseError.getMessage());
    }
});

There are probably typos in the above, but this is the general gist of it.

young_souvlaki
  • 1,886
  • 4
  • 24
  • 28
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I'm doubting my interpretation of the for each loop you wrote. Here's my shot "For every node in snapshot get it's children (in my case the children of /users would be /users/username and /users/email) and perform .child("username").getValue(); on each." Isn't snapshot already a collection of all the nodes from my ref? If so why do we need to perform getChildren()? Could you tell me how it reads? – young_souvlaki Sep 11 '15 at 17:48
  • 1
    You were responding to `child_added` events, so you get called for each child. I listen to a `value` event, which gets all children in one go. That's why I need a loop, where you don't. – Frank van Puffelen Sep 11 '15 at 18:26
  • Would you mind taking a look at my follow up question? – young_souvlaki Sep 11 '15 at 19:41
0

You can simply use a Query

Query ref = FirebaseDatabase.getInstance().getReference()
            .child("users").orderByChild("name");