I am facing a strange situation. Believe me. New to using Firestore database. I have a collection as follow,
List<String> myStringList = new ArrayList<String>();
ApiFuture<QuerySnapshot> apiFutureResults = query.get();
QuerySnapshot querySnapshot = apiFutureResults.get();
List<QueryDocumentSnapshot> documents = querySnapshot.getDocuments();
for(QueryDocumentSnapshot document: documents)
{
log.debug(document.getString("myColumnName")); //prints value successfully
String myString = document.getString("myColumnName"); // Same value assigned to String type.
log.debug("Value of myString", myString); //prints value successfully
log.debug("Result is ::",myStringList.add(myString)); // prints empty or blank.
log.debug("Arraylist size::", myStringList.size()); // Returns empty or blank.
log.debug("Arraylist content::", myStringList); // Returns empty or blank }
I was expecting true after addition. In my case I am getting myStringList.add(myString);
returns empty. String is fetched from QueryDocumentSnapshot (com.google.cloud.firestore). In What scenarios ArrayList.add() method returns empty. Or Is there any issue with this approach to fetch records from Firestore. This is not android application. Just a Java product.
Your code is printing the value returned by myStringList.add(). I suspect that's not what you intended. Printing the result of add() doesn't seem very useful for debugging purposes.
Maybe what you wanted to do is first add the item to the list first, then print the contents of the list.
myStringList.add(myString);
log.debug("Result is ::",myStringList); // print the list
You probably also want to print the list after the loop completes, not every iteration.
how to retrieve data from all documents in the first collection with a clause in the second collection.
I use this code but the data doesn't show up at all.. please help me..
dbf.collection("group").document().collection("member")
.whereEqualto("iduser", "098332")
.orderBy("updatetime", Query.Direction.DESCENDING)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value, #Nullable FirebaseFirestoreException error) {
List<DocumentSnapshot> list = value.getDocuments();
datalist.clear();
for (DocumentSnapshot d : list) {
final Modelfirestore c = d.toObject(Modelfirestore.class);
datalist.add(c);
}
mAdapterss.notifyDataSetChanged();
}
});
I've tried with the script above but it doesn't work
Every time you call document() without an argument, it generates a references to a new, unique, non-existing document in your database. So you're querying a non-existing subcollection, which explains why you don't get any results.
If you want to query the member subcollection of a specific group document, specify the ID of that group document in the call to document(...).
If you want to query across all member subcollections, you can use a collection group query.
If you want to query all member collections under groups, you can use a collection group query with the trick in Sam's answer here: CollectionGroupQuery but limit search to subcollections under a particular document
I have an android app with firebase as the backend. I have an activity where the use populates a list for a recycler view which they want to save to the database.
Below is a screenshot of the Firebase Realtime Database structure of my database:
The values of children in the Values node is what I'm using to create the id for new data that is added.
I have an arraylist of sales objects populated by the user which is to be saved in a new Sales node and would like to use the value in Values/Sales to populate the ids for each item in the arraylist.
Below is my code for saving that data. However in the database, only the last item in the arraylist is saved.
ArrayList<Inventory> salesArrayList = new ArrayList<>();
------------------- Code for populating data into the arraylist ----------------------
for (int i = 0; i < salesArrayList.size(); i++){
int finalI = i;
databaseReference.child("Values").child("Sales").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
String newCount = String.valueOf(Integer.parseInt(String.valueOf(snapshot.getValue()))+1); // Gets the original value saved and adds 1 to it to be used as the id in the 'Sales' node
DatabaseReference countReference = databaseReference.child("Sales").child(newCount);
countReference.child("date").setValue(currentDate);
countReference.child("name").setValue(salesArrayList.get(finalI).getName());
countReference.child("quantity").setValue(salesArrayList.get(finalI).getQuantity());
countReference.child("unit_price").setValue(salesArrayList.get(finalI).getValue()/salesArrayList.get(finalI).getQuantity());
countReference.child("value").setValue(salesArrayList.get(finalI).getValue());
databaseReference.child("Values").child("Sales").setValue(Integer.parseInt(newCount)); // Updates the number of sales
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
If there are 7 items in the arraylist, I'd like a way to have all them saved in the Sales similar to the Purchases node rather than only the last item on the arraylist.
This doesn't work (and I'm surprised it even compiles):
databaseReference.child("Values").child("Sales")(Integer.parseInt(newCount)); // Updates the number of sales
To write back the incremented number of sales:
snapshot.getReference().setValue(snapshot.getValue(Long.class)+1);
Note that in general using such sequential numeric keys in Firebase is an anti-pattern. I highly recommend using Firebase's native push() keys, which are also always incrementing but provide much stronger guarantees outside of that. To read more about that, check out: Best Practices: Arrays in Firebase.
I can't comment, but I can answer, but I don't know if my answer will be correct.
If you are only updating the last item on the array it would seem to me that you are at each iteration overwriting the same object and finally saving only that object to the database.
Is there an append method? Or does the documentation say anything about this?
This for me seems the most probable cause.
Maybe you each time need a new instance of the 'databaseReference'-object.
My app has to display a list of names on ListView. Those names are stored within Cloud Firestore in the following manner:
Collection: users - Documents: Organized by user UID - Field: name (I must note that there are other fields for each user too, however i need to retrieve the name field specifically)
To accomplish this, I have a first list that retrieves all documents or user UIDs. That first list is then used within a for loop to retrieve the name of each user in the users collection.
However, due to Firebase retrieving data asynchronously, some names are usually missing and they end up being displayed in a disorganized manner (not consistent with the order in which uids were passed from the first list).
If anyone could give me any insight on how to make Firebase wait for data to be retrieved before continuing with the for loop it would be greatly appreciated!
Below is some of my code to give you a better idea of what I am doing.
This first part of the code, which successfully retrieves all documents (uids) and puts them on a list
subTopicsDatabase.collection("schoolTopics").document(docKey).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
DocumentSnapshot document = task.getResult();
if (document.exists()) {
List<String> list = new ArrayList<>();
Map<String, Object> map = document.getData();
if (map != null) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
list.add(entry.getValue().toString());
}
}
}});
The second part of the code, which doesnt work due to Firebase's asynchronous behavior.
for (int i = 0; i<list.size(); i++) {
String uid = list.get(i);
Toast.makeText(TutorsListActivity.this, uid, Toast.LENGTH_LONG).show();
subTopicsDatabase.collection("users").document(uid).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists()) {
String stName = documentSnapshot.getString("name");
ArrayAdapter<String> adapter = new ArrayAdapter<>(TutorsListActivity.this, R.layout.item_subtopic, testList);
adapter.notifyDataSetChanged();
sListView2.setAdapter(adapter);
}
}
});
}
You need to store the elements and in last of the for loop, you have to show the names list.
As you said you are getting a list of All UID's now you want their names on a list. I had updated your code to work.
// Create a Hashmap Object which has Key as UID and Name as Key
HashMap<String,String> hashMap = new HashMap<>();
for (int i = 0; i<list.size(); i++) {
final String uid = list.get(i);
Toast.makeText(TutorsListActivity.this, uid, Toast.LENGTH_LONG).show();
subTopicsDatabase.collection("users").document(uid).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists()) {
//Store Your UID and Name in Hashmap
String stName = documentSnapshot.getString("name");
hashMap.put(uid,stName);
}
//Check if it is last index of array then show the names list
if(i==list.size()-1){
showListInAdapter(hashMap);
}
}
});
}
private void showListInAdapter(HashMap<String,String> hashMap) {
//now convert your hashmap into a list of name and get Your Names List and show in Adapter
ArrayList<String> listOfNames = new ArrayList<>(hashMap.keySet());
//Set list to Adapter
ArrayAdapter<String> adapter = new ArrayAdapter<>(TutorsListActivity.this, R.layout.item_subtopic, listOfNames);
sListView2.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
Your assertion that it doesn't work because of Firebase's asynchronous behavior is incorrect. The reason that your view is not displaying the way you want it to, is because you are updating your adapter every single time you receive a document back from Firebase.
In psuedocode, this is what should happen:
// Create function with completion block - i.e. fetchTopicNames
//
// Create array to hold fetched String values - i.e. topicNames
// For loop to request each document
// add String value to `topicNames`
// if current iteration is last iteration, finish forLoop and return topicNames
//
In another method, call your newly created method, update your adapter with your full list of topicNames. You can also then perform other operations on your Array like filtering and sorting. There is probably a more efficient way as well, I'm just giving you the most basic way to accomplish your task.
you can simulate fetching user synchronous by making recursion (function which call it self until index becomes bigger then size of list of uids).
So first thing you want to define adapter and List of strings (which represent user names). When you do that, you can call recursion, which will populate your List and notifyDataSetChanged. Here is the example
// Define empty list of user names, which you will populate later with recursion
List<String> userNames = new ArrayList<String>();
// Connect adapter with empty list
ArrayAdapter<String> adapter = new ArrayAdapter<>(TutorsListActivity.this, R.layout.item_subtopic, userNames);
// Set adapter to ListView
sListView2.setAdapter(adapter);
// Call recursion with list of uids and starting index of 0
getUserSync(list, 0);
private void getUserSync(List<String> list, int i) {
if (i < 0 || i > list.length - 1) {
// If index i is out of bounds for list, we break the recursion
return;
}
String uid = list.get(i);
Toast.makeText(TutorsListActivity.this, uid, Toast.LENGTH_LONG).show();
subTopicsDatabase.collection("users").document(uid).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
// When we load document, we fetch name and add it to the list which is connected to adapter
// After that, we call adapter.notifyDataSetChanged which will update ui
// When all that is done, we call getUserSync, to fetch user name for next uid
if (documentSnapshot.exists()) {
String stName = documentSnapshot.getString("name");
if (stName != null) {
userNames.add(stName);
adapter.notifyDataSetChanged();
}
}
getUserSync(list, i++);
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// If enything goes wrong, we break the recursion
return;
}
});
}
If you have any troubles, feel free to comment..
I recommend that you follow the Guide to App Architecture and use a LiveData observer to keep the ListView updated. You can follow this tutorial and insert your Firebase access in the Repository class.
Changing your code to fit the MVVM pattern may require a bit of work but it will also make your app run better and simplify some development later.
I have the following structure in Firebase:
where the entries contain an image and a timestamp. The name of each entry is randomly generated and the timestamp is an int larger than 0.
What I want:
To delete the entries with a timestamp smaller than a specific value.
My current progress:
I should be able to retrieve the list of sorted entries with the following code:
Firebase myFirebaseRef = new Firebase("https://someURL.firebaseio.com/");
Firebase listOfObjects = myFirebaseRef.orderByChild("timestamp").getRef();
What's next:
I want to iterate through and/or somehow only remove specific entries in the listOfObjects. Possibly with some if-statements?
First I believe you could use endAt() to filter just the entries you need
Firebase listOfObjects = myFirebaseRef.orderByChild("timestamp").endAt(myLimit).getRef();
Then something like this should work
listOfObjects.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot querySnapshot) {
queurySnapshot.getRef().removeValue()
}
}