Getcontactsfromfirebase() Method Return an Empty List

getContactsFromFirebase() method return an empty list

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:

  • 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)

Problem to add an object from firebase in a List

You are getting 0 because you are returning the list before the data is fetched, see that firebase runs this asynchronous and it gets some time in onDataChange() to fetch the data in order to return it. Please read this answer I posted yesterday How do I pass data out of the onDataChange method? is the same issue.

Also please check this answer from Frank van Puffelen getContactsFromFirebase() method return an empty list

Is there a way to save data from a firebase Realtime Database without getting a null variable?

When you add a Listener to Query, you don't immediately get the data back in the array list. The meaning of what you have written is:

  • Make a query on Realtime Database,
  • When a data change event is received (or when the listener is added) call the onDataChanged(DataSnapshot) function

So what you need is a callback when you get the data in the future.

That means you won't be getting the data immediately where you did:

itemsList = itemsArray; // itemsArray is null

Instead do something like:

class MyActivity extends AppCompatActivity {
private final List<Item> itemsArrayList = new ArrayList<>();

@Override

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shop);

shopRef = FirebaseDatabase.getInstance().getReference();

Query query = shopRef.child("shop");

query.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
List<Item> snapshotItems = new ArrayList<>();
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Item item = new Item();
item.setLogo(snapshot.child("Logo").getValue().toString());
item.setName(snapshot.child("Name").getValue().toString());
item.setDescription(snapshot.child("Description").getValue().toString());

snapshotItems.add(item);
}
// Calling this function will add the data back to the list
MyActivity.this.onItemsObtained(snapshotItems);
}

@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}

public void onItemsObtained(List<Item> items) {
itemsArrayList.clear(); // To remove old Data
itemsArrayList.addAll(items);

// Continue your own logic...
}

}

If getIntent is null Firebase

Your problem is in this line

post_title = getIntent().getExtras().get("post_title").toString();

When "post_title" does not exist, get("post_title") returns null. Then you get a NullPointerException because you call toString() on it. You can also see this in the error message

Attempt to invoke virtual method 'java.lang.String java.lang.Object.toString()' on a null object

The fix for this is to add some null checks where you extract post_title to guard against 1) having no extras (Bundle being null), 2) having no post_title entry, and 3) having a post_title entry that cannot be cast to a string.

This would look like:

String post_title = "";
Bundle b = getIntent().getExtras();
if( b != null ) {
post_title = b.getString("post_title");
}

if( post_title == null || post_title.isEmpty() ) {
// call firebase
}
else {
txttitle.setText(post_title);
}

Alternately, you can just use getStringExtra, which will do the same checks internally for you, and return null if the extras are missing or the post_title entry is missing or not a String.

String post_title = getIntent().getStringExtra("post_title");

if( post_title == null || post_title.isEmpty() ) {
// call firebase
}
else {
txttitle.setText(post_title);
}

Index:0 Size:0 when fetching item from firebase

Getting data from Firebase Realtime Database is an async task. So,

Log.d("mString", String.valueOf(strings.size()));
return strings.get(0);

might be getting called before the code inside addOnCompleteListener.

If you want to have your getVCount to have a way to return the result, one way to do this is by creating your own callback like this:

public interface Callback {
void onCallback(String result);
}
private void getVCount(String mPath, Callback callback) {
ArrayList<String> strings = new ArrayList<>();
FirebaseDatabase.getInstance().getReference("cars").child(mPath).child("maserati")
.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
@Override
public void onComplete(@NonNull @NotNull Task<DataSnapshot> task) {
if(task.isSuccessful()){
strings.add(task.getResult().getValue().toString());
}else{
strings.add("0");
}
Log.d("mString", String.valueOf(strings.size()));
callback.onCallback(strings.get(0));
}
});
}

And call it like this:

getVCount(new Callback() {
@Override
public void onCallback(String result) {
Log.d(TAG, result);
}
});


Related Topics



Leave a reply



Submit