Reputation: 1391
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
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