Andrew Lam
Andrew Lam

Reputation: 1391

Display data fetched from multiple Firebase database references with an arrayAdapter

I'm working on Android with Firebase. I want to display a list of information with an arrayAdapter from a database. I'm using an ArrayList of uids to fetch some user information from a denormalized Firebase database structured like so:

name  : {
 uid1:"bear",
 uid2:"salmon"
},

phone : {
 uid1:"888-395-4023",
 uid2:"123-456-6789"
}

My current approach is to store the fetched values into a UserInfo object of a class like this.

public UserInfo (String name, String phone) 

and then add each object onto the arrayAdapter.

someArrayAdapter.add(userInfo);

My problem is with more than one user, and reading data from more than one reference, in my following implementation all the fields in the added userInfo are empty because event listener callbacks are received after the add() method call (data is not updated when the callbacks are received)

private FirebaseDatabase mFirebaseDatabase = new FirebaseDatabase.getInstance();
private ValueEventListener mNameListener;
private ValueEventListener mPhoneListener;
private DatabaseReference mNameRef;
private DatabaseReference mPhoneRef;

private void getUserInfo (@NonNull final ArrayList<String> uidList)
{
     // This for-loop loops through the list of strings, and use the string to construct new database references.

     for (String uid : uidList)
     {
          // Create a new userInfo object
          UserInfo userInfo = new UserInfo();

          // Create a database reference for accessing the name
          mNameRef = mFirebaseDatabase.getReference().child("name").child(uid);

          // Create a database reference for accessing the name
          mPhoneRef = mFirebaseDatabase.getReference().child("phone").child(uid);

          // My name listener
          mNameListener = new ValueEventListener()
          {
               @Override
               public void onDataChange(final DataSnapshot dataSnapshot) {
                    String name = DataSnashot.getValue().toString();
                    userInfo.setName(name);
               }
               // ...skipped irrelevant override 
          }

          // My phone listener
          mPhoneListener = new ValueEventListener()
          {
               @Override
               public void onDataChange(final DataSnapshot dataSnapshot) {
                    String phone = DataSnashot.getValue().toString();
                    userInfo.setPhone(phone);

                    // Log to try getting the phone from the object, as expected, is good
                    Log.i(TAG, userInfo.getPhone());
               }
               // ...skipped irrelevant override 
          }

          // Attach the listeners
          mNameRef.addListenerForSingleValueEvent(mNameListener);
          mPhoneRef.addListenerForSingleValueEvent(mPhoneListener);

          // Problem here: listener callbacks are received after this method fires.
          someArrayAdapter.add(userInfo);

          // Log, returns empty for both fields, and this log message runs before the callback log messages above.
          Log.i(TAG, userInfo.getName() + " " + userInfo.getPhone());

     }
}

I want to have a "complete" userInfo object before the for-loop goes on to the next uid. And, how should I manage so that the adapter would only add the userInfo object only if all callbacks are received?

I definitely need a better way to make the data persistent, but as it stands, I do feel is much easier for maintenance if I can just deal with simple objects. Please forgive me for any syntax or missing ";" problems, I didn't write the sample code on an IDE :)

Upvotes: 0

Views: 2005

Answers (1)

Umar Karimabadi
Umar Karimabadi

Reputation: 980

Fortunately, the callbacks are firing but they are asynchronous. This means that the userinfo object on the main thread which you initialized in the beginning of the for loop is immediately added because Firebase runs on separate threads.

   UserInfo userInfo = new UserInfo();
   ..ASYNC FIREBASE CODE
   someArrayAdapter.add(userInfor);

Since, the firebase callbacks are async, they run on their own threads and your user info is immediately added. As your userInfo currently has no information then essentially an empty user info object is added.

What you want can be achieved like this. You add your user info object inside the callback itself.

  mNameListener = new ValueEventListener()
      {
           @Override
           public void onDataChange(final DataSnapshot dataSnapshot) {
                String name = DataSnashot.getValue().toString();
                userInfo.setName(name)
                // Here you make a new event listner

                mPhoneRef.addSingleValueEventListener(new ValueEventListener(){

    onDataChange(DatasnapShot datasnapshot){
     String phone = DataSnashot.getValue().toString();
     userInfo.setPhone(phone);
     someAdapater.add(userInfo); // add your user info here
    }

    mNameRef.addListenerForSingleValueEvent(mNameListener);

    //someArrayAdapter.add(userInfor); // Do not have this here anymore

Inside the first listener, you make attach another listener to get the phone ref. once you have the value you then add it to your adapter.

However, you should reconsider re designing your database. Your database is incredibly inefficient. You do not need to make an additional query and you should not be making an additional query. You should bundle push your UserInfo objects under one node and retrieve them as you like.

An example of how you could do this is by using push keys

UserInfo userinfo = new UserInfo("Name","133405-333")
root.child("userInfo").push().setValue(userInfo);

Here instead of separating your information, you put it all under one node. This is a much database structure as you can make specific queries without the need of having to query another part of your database.

Upvotes: 1

Related Questions