Index:0 Size:0 when fetching item from firebase [duplicate] - java

This question already has answers here:
getContactsFromFirebase() method return an empty list
(1 answer)
Setting Singleton property value in Firebase Listener
(3 answers)
Closed last year.
I am trying to fetch a value from firebase like so:
private String getVCount(String mPath) {
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()));
return strings.get(0);
}
I double checked the path, and even added an else condition just to make it so that the strings array list has at least one value in it.
The value in the database does exist, and does not exist in some cases--hence the else statement to add a default value of 0. I'm not sure if this is the correct way to do that though.
Additionally, I'm still getting the error: Index: 0 Size: 0--meaning that the strings array list is empty.
Any idea why this may be so?
Thanks.

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

Array call returns null although I have updated data [duplicate]

Before adding a new data into the firestore, i want to check already a data of the same kind exists in the database or not.if already a data was present means i want to prevent the user from entering duplicate data in the database.
In my case it is like a appointment booking if already a booking for the same time exists,i want to prevent to users to book on the same time.i tried using query function but it is not preventing duplicate data entering.someone plz help me
private boolean alreadyBooked(final String boname, final String bodept, final String botime) {
final int[] flag = {0};
CollectionReference cref=db.collection("bookingdetails");
Query q1=cref.whereEqualTo("time",botime).whereEqualTo("dept",bodept);
q1.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (DocumentSnapshot ds : queryDocumentSnapshots) {
String rname, rdept, rtime;
rname = ds.getString("name");
rdept = ds.getString("dept");
rtime = ds.getString("time");
if (rdept.equals(botime)) {
if (rtime.equals(botime)) {
flag[0] = 1;
return;
}
}
}
}
});
if(flag[0]==1){
return true;
}
else {
return false;
}
}
Loading data from Cloud Firestore happens asynchronously. By the time you return from alreadyBooked, the data hasn't loaded yet, onSuccess hasn't run yet, and flag still has its default value.
The easiest way to see this is with a few log statements:
private boolean alreadyBooked(final String boname, final String bodept, final String botime) {
CollectionReference cref=db.collection("bookingdetails");
Query q1=cref.whereEqualTo("time",botime).whereEqualTo("dept",bodept);
System.out.println("Starting listener");
q1.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
System.out.println("Got data from Firestore");
}
});
System.out.println("Returning");
}
If you run this code it will print:
Starting listener
Returning
Got data from Firestore
That's probably not the order you expected. But it perfectly explains why you always get false when calling alreadyBooked: the data simply didn't come back from Firestore in time.
The solution for this is to change the way you think about the problem. Your current code has logic: "First check if it is already booked, then add a new item". We need to reframe this as: "Start checking if it is already booked. Once we know that is isn't, add a new item." In code this means that all code that needs data from Firestore must be inside the onSuccess or must be called from there.
The simplest version is to move the code into onSuccess:
private void alreadyBooked(final String boname, final String bodept, final String botime) {
CollectionReference cref=db.collection("bookingdetails");
Query q1=cref.whereEqualTo("time",botime).whereEqualTo("dept",bodept);
q1.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
boolean isExisting = false
for (DocumentSnapshot ds : queryDocumentSnapshots) {
String rname, rdept, rtime;
rname = ds.getString("name");
rdept = ds.getString("dept");
rtime = ds.getString("time");
if (rdept.equals(botime)) {
if (rtime.equals(botime)) {
isExisting = true;
}
}
}
if (!isExisting) {
// TODO: add item to Firestore
}
}
});
}
While this is simple, it makes alreadyBooked less reusable since now it contains the code to insert the new item too. You can solve this by defining your own callback interface:
public interface AlreadyBookedCallback {
void onCallback(boolean isAlreadyBooked);
}
private void alreadyBooked(final String boname, final String bodept, final String botime, AlreadyBookedCallback callback) {
CollectionReference cref=db.collection("bookingdetails");
Query q1=cref.whereEqualTo("time",botime).whereEqualTo("dept",bodept);
q1.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (DocumentSnapshot ds : queryDocumentSnapshots) {
String rname, rdept, rtime;
rname = ds.getString("name");
rdept = ds.getString("dept");
rtime = ds.getString("time");
if (rdept.equals(botime)) {
if (rtime.equals(botime)) {
isExisting = true;
}
}
}
callback.onCallback(isExisting)
}
});
}
And then you call it as:
alreadyBooked(boname, bodept, botime, new AlreadyBookedCallback() {
#Override
public void onCallback(boolean isAlreadyBooked) {
// TODO: insert item
}
});
Also see (many of these are for the Firebase Realtime Database, where the same logic applies):
getContactsFromFirebase() method return an empty list
Doug's blog post on asynchronous callbacks
Setting Singleton property value in Firebase Listener
Android + Firebase: synchronous for into an asynchronous function
Is it possible to synchronously load data from Firebase?
Querying data from firebase

Firebase Database for Android, trouble retrieving data

So I'm having trouble with retrieving data from my database. I think the problem lies in the return statement.
When I run the app the debug text where the flatID should be shown is blank. I know for sure that the data is on the database so that isn't the issue.
I'm still very new to Java and programming in general, thanks for your help and your patience :) The code is as follows.
flatID = readFlatID();
debug1.setText(flatID);
public String readFlatID(){
String uid = mAuth.getCurrentUser().getUid().toString();
mDatabase.child("Users").child(uid).child("Flat")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
flat = dataSnapshot.getValue().toString();
Toast.makeText(getApplicationContext(),"Data Read Successfully",Toast.LENGTH_SHORT).show();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return flat;
}
Firebase methods are Asynchronous. That's why the text was not set. You can set up a listener and pass that to the method such that you get a callback when the data is fetched. Here is what I mean code-wise:
//Create an interface
interface IDatabaseLoad {
void onDataLoadSuccess(String data);
void onDataLoadFailed();
}
//Initialize it
IDatabaseLoad databaseLoad = new IDatabaseLoad() {
#Override
public void onDataLoadSuccess(String data) {
debug1.setText(data);
Toast.makeText(getApplicationContext(),"Data Read Successfully",Toast.LENGTH_SHORT).show();
}
#Override
public void onDataLoadFailed() {
}
};
//modify your method
public void readFlatID(final IDatabaseLoad listener){
String uid = mAuth.getCurrentUser().getUid().toString();
mDatabase.child("Users").child(uid).child("Flat")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
listener.onDataLoadSuccess(dataSnapshot.getValue().toString());
}
#Override
public void onCancelled(DatabaseError databaseError) {
listener.onDataLoadFailed();
}
});
}
Now you just need to call readFlatID(databaseLoad) and the TextView will be set with the data after it gets fetched.
As fetching data from Firebase server is performed on the background thread on mobile devices, it takes time for the app to actually load the whole data. That is why return flat; will return nothing at the time the code is executed.
The simplest solution for you is, you have to put all what you want to do with the retrieved data inside the onDataChange() method. You can do something like this:
public void readFlatID() {
String uid = mAuth.getCurrentUser().getUid().toString();
mDatabase.child("Users").child(uid).child("Flat")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
flat = dataSnapshot.getValue().toString();
debug1.setText(flat);
Toast.makeText(getApplicationContext(), "Data Read Successfully", Toast.LENGTH_SHORT).show();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
You cannot return something now that hasn't been loaded yet. The onDataChange() method has an asynchronous behaviour which means that is called even before you are trying to get the flat object from the database. That's why it is always null outside onDataChange() method. With other words, by the time you are returning the flat object, the data has not finished loading yet from the database, so a quick solve for this would be to use the value only inside the onDataChange() method or if you want to use it outside, dive into the asynchronous world and create your own callback as explained in the last part of my answer from this post.

Pagination not working using Firebase database

I have a problem with firebase pagination.
I have table posts and this is an example structure:
and I want to get only 10 post every time, here is my code page is 0:
#NonNull
#CheckResult
public Single<DataSnapshot> getData(#NonNull DatabaseReference ref, int page) {
return Single.create(emitter -> {
ref.orderByChild("timestamp")
.startAt(page * 10)
.limitToFirst(10);
final ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (!emitter.isDisposed()) {
emitter.onSuccess(dataSnapshot);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
if (!emitter.isDisposed()) {
emitter.onError(databaseError.toException());
}
}
};
ref.addListenerForSingleValueEvent(listener);
});
}
and here is the result
Why I have list size equals 20 not 10? p.s. limit to first or limit to last it's no difference with the result
Calling startAt(), limitToFirst() and similar methods on a DatabaseReference returns a new Query object. You need to keep a reference to that Query and attach your listeners to that:
Query query = ref.orderByChild("timestamp")
.startAt(page * 10)
.limitToFirst(10);
query.addListenerForSingleValueEvent(listener);

Firebase/Android: Adding retrieved values from Firebase to arraylist returns null pointer exception

I'm trying the add the retrieved values from Firebase database to an Arraylist and from there to a String array. My retrieval method works fine. I can have all the values printed out in a toast. But apparently it doesn't get added to the arraylist.
Here's my code for retrieval in onActivityCreated() of fragment class.
ArrayList<String> allBrands = new ArrayList<>();
brandRef=FirebaseDatabase.getInstance().getReferenceFromUrl("https://stockmanager-142503.firebaseio.com/Brands");
q=brandRef.orderByChild("brandName");
q.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
allBrands.add((dataSnapshot.getValue(Brand.class)).getBrandName());
Toast.makeText(getActivity(),(dataSnapshot.getValue(Brand.class)).getBrandName(), Toast.LENGTH_SHORT).show();
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
And this is where I'm trying to use the arrayList in OnActivityResult() method of the Fragment class but the iterator loop is not executed I believe. The toast is not seen. I'm getting a null pointer exception when I try to work with the array. I assume the values do not get copied to the brands array.
count=allBrands.size();
String[] brands=new String[count];
Iterator<String> itemIterator = allBrands.iterator();
if(itemIterator.hasNext()){
//brands[i] = itemIterator.next();
Toast.makeText(getActivity(), itemIterator.next(), Toast.LENGTH_SHORT).show();
// i++;
}
for( i=0;i<count;i++){
if(brands[i].compareTo(Brand)==0){
f=1;break;
}
}
Here's my database in case that helps. But I can print out all the retrieved values in a Toast with no problem.
It's hard to be certain from the code you shared, by I suspect you may be bitten by the fact that all data is loaded from Firebase asynchronously. Alternatively you may simply not have permission to read the data. I'll give an answer for both.
Data is loaded asynchronously
It's easiest to understand this behavior when you add a few log statements to a minimal snippet of your code:
System.out.println("Before attaching listener");
q.addChildEventListener(new ChildEventListener() {
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
System.out.println("In onChildAdded");
}
public void onChildChanged(DataSnapshot dataSnapshot, String s) { }
public void onChildRemoved(DataSnapshot dataSnapshot) { }
public void onChildMoved(DataSnapshot dataSnapshot, String s) { }
public void onCancelled(DatabaseError databaseError) { }
});
System.out.println("After attaching listener");
The output of this snippet will be:
Before attaching listener
After attaching listener
In onChildAdded (likely multiple times)
This is probably not the order you expected the output in. This is because Firebase (like most cloud APIs) loads the data from the database asynchronously: instead of waiting for the data to return, it continues to run the code in the main thread and then calls back into your ChildEventListener.onChildAdded when the data is available.
There is no way to wait for the data on Android. If you'd do so, your users would get the daunted "Application Not Responding" dialog and your app would be killed.
So the only way to deal with the asynchronous nature of this API is to put the code that needs to have the new data into the onChildAdded() callback (and likely into the other callbacks too at some point):
q.addChildEventListener(new ChildEventListener() {
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
allBrands.add((dataSnapshot.getValue(Brand.class)).getBrandName());
System.out.println(allBrands.length);
}
You need permission to read the data
You need permission to read the data from a location. If you don't have permission, Firebase will immediately cancel the listener. You need to handle this condition in your code, otherwise you'll never know.
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
Try this (I'm writing this to future reference of myself .. too)
As you can see we implement a reresh before it's end. There is probably a nicer way to do it. However, it is not documented. Also all this event Listners should be add autmoatically and released automatically by firebase but they don't do it from some reason.
/**
* #param uid User's ID
* #param Callable send as null just to implement a call to assure the callback is updapted before it's finished
* #return returns ArrayList of all Games unique identification key enlisted in a User
*/
private final ArrayList<String> mGamesPlaying = new ArrayList<>();
public ArrayList<String> mGamesPlaying(final String uid, final Callable refresh) {
final Firebase ref = FirebaseRef;
Firebase usersRef = ref.child("users").child(uid).child("playing_games");
usersRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
mGamesPlaying.clear();
for (DataSnapshot child : snapshot.getChildren()) {
Log.d(TAG, "Test Game" + child.getKey());
mGamesPlaying.add(child.getKey());
}
try {
refresh.call();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
return mGamesPlaying;
}

Check if ID exists in Firebase Android

I am writing an android app and I want to check if a key exists in order to avoid duplicate values. I´ve been investigating but it looks that all I can add is listeners, when I just want to check if an ID exists or not already.
Taking this SO question as an example, I would like to know if -JlvccKbEAyoLL9dc9_v exists. How can I do this?
Thanks in advance.
The approach will always be similar to what I wrote in this answer about JavaScript: Test if a data exist in Firebase
ref.child("-JlvccKbEAyoLL9dc9_v").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
// TODO: handle the case where the data already exists
}
else {
// TODO: handle the case where the data does not yet exist
}
}
#Override
public void onCancelled(FirebaseError firebaseError) { }
});
But keep in mind that push ids in Firebase exist to prevent having to do this sort of check. When multiple clients generate push ids, they are statistically guaranteed to be unique. So there's no way one of them can create the same key as another.
Any case where you need to check if an item already exists is likely to have race conditions: if two clients perform this check almost at the same time, neither of them will find a value.
RxJava 2 :
public static Observable<Boolean> observeExistsSingle(final DatabaseReference ref) {
return Observable.create(emitter ->
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
emitter.onNext(dataSnapshot.exists());
emitter.onComplete();
}
#Override
public void onCancelled(DatabaseError databaseError) {
emitter.onError(databaseError.toException());
}
}));
}
Usage:
public Observable<Boolean> isYourObjectExists(String uid) {
return observeExistsSingle(databaseReference.child(uid));
}
In your class:
yourRepo.isYourObjectExists("-JlvccKbEAyoLL9dc9_v")
.subscribe(isExists -> {}, Throwable::printStackTrace);
Based on #Frank van Puffelen's answer, here's a few lines to see if a ref - itself - exists before using it.
public void saveIfRefIsAbsent(DatabaseReference firebaseRef) {
DatabaseReference parentRef = firebaseRef.getParent();
String refString = firebaseRef.toString();
int lastSlashIndex = refString.lastIndexOf('/');
String refKey = refString.substring(lastSlashIndex + 1);
parentRef.child(refKey).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
// TODO: handle the case where the data already exists
}
else {
// TODO: handle the case where the data does not yet exist
}
}
#Override
public void onCancelled(FirebaseError firebaseError) { }
});
}
In my case I have a Util to create the schema programmatically. I use this in order to add new data without overwriting existing data.

Categories