I am using Firestore & Java.
I am trying to change multiple values by query and batch.
My DB
Kullanıcılar
-deneme#deneme.com
--Hayvanlar
---RandomId1
"ozel", "E65" //I WANNA UPDATE THIS (First))
---RandomId2
----Dogru
-----RandomId
------Acep
"ozel", "E65" //AND THIS (Second)
My Code
CollectionReference cr = fs.collection("Kullanıcılar/" + deneme#deneme.com + "/Hayvanlar");
Query query = cr.whereEqualTo("ozel", "E65");
query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
WriteBatch batch = fs.batch();
for (DocumentSnapshot ds : task.getResult()) {
batch.update(ds.getReference(), "ozel", "E75");
}
batch.commit();
}
}
});
When i run this codes my only first value updates.
I never reach to second value :/
Move batch.commit() outside your for loop
I am new to java. I have a firestore_member_list. In firestore_member_list, it contains values: ["steve","ram","kam"]. I am using for loop to pass values one by one.
loadingbar.show()
for( int k=0; k<firestore_member_list.size();k++){
String member_name = firestore_member_list.get(k);
final DocumentReference memDataNameCol = firestoredb.collection("member_collection").document(member_name);
memDataNameCol.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
// In here, I am retreiveing the document data from firestore and assigning it to the ArrayList which is `all_mem_data`
all_mem_data.add(document.get("member_name").toString());
all_mem_data.add(document.get("member_address").toString());
Toast.makeText(getActivity(), "all mem data array"+all_mem_data.toString(),
Toast.LENGTH_LONG).show();
}
}
}
});
}
Log.d("all_mem_data",all_mem_data)
loadingbar.hide()
I know firestore executes asynchronously. Since firestore retrieves data asynchronous, before filling the array of all_mem_data, the last line gets executed and shows the empty array. How to wait for the array to get filled and after filling,execute the last two lines. Please help me with solutions.
Any code that needs the data from the database, needs to be inside the onComplete that fires when that data is available.
If you want to wait until all documents are loaded, you can for example keep a counter:
loadingbar.show()
int completeCount = 0;
for( int k=0; k<firestore_member_list.size();k++){
String member_name = firestore_member_list.get(k);
final DocumentReference memDataNameCol = firestoredb.collection("member_collection").document(member_name);
memDataNameCol.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
...
if (completeCount++ == firestore_member_list.size()-1) {
Log.d("all_mem_data",all_mem_data)
loadingbar.hide()
}
}
});
}
I need after inner foreach finish and add all data in list then send to interface
but when but this line " view.setOrders(orderList); " below " orderList.add(order); "
my code run okye but not that is not performance , I need best way to collection list then send to interface ..
public ListenerRegistration getOrders() {
view.showLoading();
ListenerRegistration listenerRegistration = refOrders.addSnapshotListener((queryDocumentSnapshots, e) -> {
view.hideLoading();
if (e != null) {
view.onErrorMessage(e.getMessage());
} else {
List<Order> orderList = new ArrayList<>();
for (QueryDocumentSnapshot snapshot : queryDocumentSnapshots) {
Order order = snapshot.toObject(Order.class);
order.setOrderId(snapshot.getId());
refUsers.document(order.getPhone()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
User user = task.getResult().toObject(User.class);
order.setName(user.getName());
order.setAddress(user.getAddress());
orderList.add(order);
}
}
});
}
//Here Back List size = 0
view.setOrders(orderList);
}
});
return listenerRegistration;
}
Since get() method is asynchronous which means that the code after the CompletionListener will be executed first and then after the data is retrieved the listener will get executed.
Therefore to solve the issue add the following line view.setOrders(orderList); inside the CompletionListener
if (task.isSuccessful()) {
User user = task.getResult().toObject(User.class);
order.setName(user.getName());
order.setAddress(user.getAddress());
orderList.add(order);
view.setOrders(orderList);
pls help me with little issue, im sure that you can do it :D
Im trying to set up a field on a firestore document "user_cases_information" with a field "case_number"
First i declare this global var
private String case_number_consecutive = new String("");
in .setOnClickListener i have this: (to read a old consecutive or case number from other document)
if (service_type.equals("support")){
DocumentReference docRef = firestore_popup.collection("param_data_app").document("support_case_numbers");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
String consecutive = document.get("consecutive").toString();
if (consecutive.equals(null)) {
int random = new Random().nextInt(117) + 4;
case_number_consecutive = "IOC-09"+random;
} else {
case_number_consecutive = consecutive; UpdateCaseNumbersConsecutive("support");
}
} else {
int random = new Random().nextInt(117) + 4;
case_number_consecutive = "IOC-09"+random;
}
}
});
}
Map<String, Object> user_cases_information = new HashMap<>();
user_cases_information.put("case_number", case_number_consecutive);
... (then i use a firestore reference to put the new case number)
It supposed that when i check firestore, precisely the document where i set the "user_cases_information" document, i should see the number or the new case_number, but i always get a null reference, the field consecutive is always = null
Why im getting this null? i used a Toast and put it before this:
Toast.makeText(...."Value: "+case_number_consecutive
Map<String, Object> user_cases_information = new HashMap<>();
user_cases_information.put("case_number", case_number_consecutive);
and it show null so the value or data is losing inside the above functions
how can i fix this??? thank you a lot.
The reason for this, is because firebase is executing asynchronously, and you are coding as if it all happens synchronously. What does this mean ? It means that the firebase call does not immediately return a value from the server, it takes some time to get that value, but the rest of your code is executing before the response comes back, and that's why you're having problems. So, even though this code executes:
if (service_type.equals("support")){
DocumentReference docRef = firestore_popup.collection("param_data_app").document("support_case_numbers");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
String consecutive = document.get("consecutive").toString();
if (consecutive.equals(null)) {
int random = new Random().nextInt(117) + 4;
case_number_consecutive = "IOC-09"+random;
} else {
case_number_consecutive = consecutive; UpdateCaseNumbersConsecutive("support");
}
} else {
int random = new Random().nextInt(117) + 4;
case_number_consecutive = "IOC-09"+random;
}
}
});
}
by the time a response comes back, your other code :
Map<String, Object> user_cases_information = new HashMap<>();
user_cases_information.put("case_number", case_number_consecutive);
has already been completed and called, and then you never get the actual value from firebase.
have a look at my answer here : How to add results of Facebook Graph api to array list in Java and I'll help you make something similar if you need help
here's something you can do :
interface Callback{
void firebaseResponseCallback(String result);//whatever your return type is.
}
change the signature of this method, to be something like this :
public void getCaseNumber(Callback callback) {
change your code to this:
if (service_type.equals("support")){
DocumentReference docRef = firestore_popup.collection("param_data_app").document("support_case_numbers");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
String consecutive = document.get("consecutive").toString();
if (consecutive.equals(null)) {
int random = new Random().nextInt(117) + 4;
case_number_consecutive = "IOC-09"+random;
} else {
case_number_consecutive = consecutive; UpdateCaseNumbersConsecutive("support");
}
} else {
int random = new Random().nextInt(117) + 4;
case_number_consecutive = "IOC-09"+random;
}
callback.firebaseResponseCallback(case_number_consecutive);
}
});
}
then, when you call getCaseNumber:
getCaseNumber(new Callback() {
#Override
public void firebaseResponseCallback(String result) {
here, this result parameter that comes through is your api call result to use, so result will be your case number, so make your objects inside this method, not outside
}
});
}
Edit
I've made this post specifically for these types of questions :
How to get data from any asynchronous operation in android
There is a mistake you are doing in that code.
According to firebase latest Version, You can use the value/ String of firebase database only inside the .addOnCompleteListener/.addValueEvent/etc Function.
DocumentReference docRef = firestore_popup.collection("param_data_app").document("support_case_numbers");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
/////////////////////////////call here everything related to it////////////////////////
}
});
}
Outside the firebase add complete listener or add value listener, The variable or object value will not change or remain same!
Consider a collection of users. Each document in the collection has name and email as fields.
{
"users": {
"uid1": {
"name": "Alex Saveau",
"email": "saveau.alexandre#gmail.com"
},
"uid2": { ... },
"uid3": { ... }
}
}
Consider now that with this working Cloud Firestore database structure I launch my first version of a mobile application. Then, at some point I realize I want to include another field such as last_login.
In the code, reading all the users documents from the Firestore DB using Java would be done as
FirebaseFirestore.getInstance().collection("users").get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
mUsers.add(document.toObject(User.class));
}
}
}
});
where the class User contains now name, email and last_login.
Since the new User field (last_login) is not included in the old users stored in the DB, the application is crashing because the new User class is expecting a last_login field which is returned as null by the get() method.
What would be the best practice to include last_login in all the existing User documents of the DB without losing their data on a new version of the app? Should I run an snippet just once to do this task or are there any better approaches to the problem?
You fell into a gap of NOSQL databases: Document oriented databases do not guarantee structural integrity of the data (as RDBMS do)
The deal is:
in an RDBMS all stored data have the same structure at any given time (within the same instance or cluster). When changing the structure (ER-diagram) you have to migrate the data for all existing records which costs time and effort.
As a result, your application can be optimized for the current version of the data structure.
in a Document oriented database each record is an independent "Page" with its own independent structure. If you change the structure it only applies to new documents. So you don't need to migrate the existing data.
As a result, your application must be able to deal with all versions of the data structure you've ever used in your current database.
I don't know about firebase in detail but in general you never update a document in a NOSQL database. You only create a new version of the document. So even if you update all documents your application must be prepared to deal with the "old" data structure...
I wrote some routines to help automate this process back when I posted the question. I did not post them since these are a bit rudimentary and I was hoping for an elegant Firestore-based solution. Because such solution is not still available, here are the functions I wrote.
In short, we have functions for renaming a field, adding a field, or deleting a field. To rename a field, different functions are used depending on the data type. Maybe someone could generalise this better? The functions below are:
add_field: Adds a field in all documents of a collection.
delete_field: Deletes a field in all documents of a collection.
rename_*_field: Renames a field containing a certain data type (*) in all documents of a collection. Here I include examples for String, Integer, and Date.
Add field:
public void add_field (final String key, final Object value, final String collection_ref) {
FirebaseFirestore.getInstance().collection(collection_ref).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
WriteBatch batch = db.batch();
for (DocumentSnapshot document : task.getResult()) {
DocumentReference docRef = document.getReference();
Map<String, Object> new_map = new HashMap<>();
new_map.put(key, value);
batch.update(docRef, new_map);
}
batch.commit();
} else {
// ... "Error adding field -> " + task.getException()
}
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// ... "Failure getting documents -> " + e
}
});
}
Delete field:
public void delete_field (final String key, final String collection_ref) {
FirebaseFirestore.getInstance().collection(collection_ref).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
WriteBatch batch = db.batch();
for (DocumentSnapshot document : task.getResult()) {
DocumentReference docRef = document.getReference();
Map<String, Object> delete_field = new HashMap<>();
delete_field.put(key, FieldValue.delete());
batch.update(docRef, delete_field);
}
// Commit the batch
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// ...
}
});
} else {
// ... "Error updating field -> " + task.getException()
}
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// ... "Failure getting notices -> " + e
}
});
}
Rename field:
public void rename_string_field (final String old_key, final String new_key, final String collection_ref) {
FirebaseFirestore.getInstance().collection(collection_ref).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
WriteBatch batch = db.batch();
for (DocumentSnapshot document : task.getResult()) {
DocumentReference docRef = document.getReference();
String old_value = document.getString(old_key);
if (old_value != null) {
Map<String, Object> new_map = new HashMap<>();
new_map.put(new_key, old_value);
Map<String, Object> delete_old = new HashMap<>();
delete_old.put(old_key, FieldValue.delete());
batch.update(docRef, new_map);
batch.update(docRef, delete_old);
}
}
// Commit the batch
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// ...
}
});
} else {
// ... "Error updating field -> " + task.getException()
}
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// ... "Failure getting notices ->" + e
}
});
}
public void rename_integer_field (final String old_key, final String new_key, final String collection_ref) {
FirebaseFirestore.getInstance().collection(collection_ref).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
WriteBatch batch = db.batch();
for (DocumentSnapshot document : task.getResult()) {
DocumentReference docRef = document.getReference();
int old_value = document.getDouble(old_key).intValue();
Integer ov = old_value;
if (ov != null) {
Map<String, Object> new_map = new HashMap<>();
new_map.put(new_key, old_value);
Map<String, Object> delete_old = new HashMap<>();
delete_old.put(old_key, FieldValue.delete());
batch.update(docRef, new_map);
batch.update(docRef, delete_old);
}
}
// Commit the batch
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// ...
}
});
} else {
// ... "Error updating field -> " + task.getException()
}
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// ... "Failure getting notices -> " + e
}
});
}
public void rename_date_field (final String old_key, final String new_key, final String collection_ref) {
FirebaseFirestore.getInstance().collection(collection_ref).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
WriteBatch batch = db.batch();
for (DocumentSnapshot document : task.getResult()) {
DocumentReference docRef = document.getReference();
Date old_value = document.getDate(old_key);
if (old_value != null) {
Map<String, Object> new_map = new HashMap<>();
new_map.put(new_key, old_value);
Map<String, Object> delete_old = new HashMap<>();
delete_old.put(old_key, FieldValue.delete());
batch.update(docRef, new_map);
batch.update(docRef, delete_old);
}
}
// Commit the batch
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// ...
}
});
} else {
// ... "Error updating field -> " + task.getException()
}
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// ... "Failure getting notices -> " + e
}
});
}
Just wanted to share because I read that you were hoping for a Firestore based solution.
This worked for me. forEach will query each document in the collection and you can manipulate as you like.
db.collection("collectionName").get().then(function(querySnapshot) {
querySnapshot.forEach(async function(doc) {
await db.collection("collectionName").doc(doc.id).set({newField: value}, {merge: true});
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
});
To solve this, you need to update each user to have the new property and for that I recommend you to use a Map. If you are using a model class when you are creating the users as explained in my answer from this post, to update all users, just iterate over the users collection amd use the following code:
Map<String, Object> map = new HashMap<>();
map.put("timestamp", FieldValue.serverTimestamp());
userDocumentReference.set(map, SetOptions.merge());
I am guessing that last_login is a primitive data type, maybe a long to hold a timestamp. An auto-generated setter would look like this:
private long last_login;
public void setLast_login(long last_login) {
this.last_login = last_login;
}
This leads to a crash when old documents that lack the field are fetched due to null assignment to a variable of a primitive data type.
One way around it is to modify your setter to pass in a variable of the equivalent wrapper class - Long instead of long in this case, and put a null check in the setter.
private long last_login;
public void setLast_login(Long last_login) {
if(last_login != null) {
this.last_login = last_login;
}
}
The cost of avoiding the null pointer exception is the boxing-unboxing overhead.