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
Possible Unhandled Promise Rejection (Id:0) Error: Network Error in React Native
Java.Io.Filenotfoundexception Open Failed Eacces (Permission Denied) on Device
Pdf Not Open in Webview from Url
Retrofit 2 Json Response Json Array and Object
Images Are Not Visible in React-Native App Release Build
Android Studio - Textview Not Showing in Design View Layout
Android Textview Text Not Getting Wrapped
Android:Strike Out Text With Bold or Thicker Line Than Default Strike_Thru_Text_Flag
Alarm Manager Won't Work When the App Is Killed in the Background and Device Is Locked
Onclicklistener Does Not Work in Fragment
Import Android.Support.V7.App Cannot Be Resolved
Ship an Application With a Database
How to Get Screen Dimensions as Pixels in Android
What Is the Simplest and Most Robust Way to Get the User'S Current Location on Android
How to Get the Current Gps Location Programmatically in Android