How to avoid index in firestore? - android

I have created a database in firestore and loaded the test data successfully. But later I noticed that some data is missing in database. I found that when I load the same records with a different value for some fields the old record is replaced with the new record. I feel like this is the issue in the database design. I need to collect and save all the records even if it is a duplicate records at entire records level.
Could you please let me know how to do this ?
private void exportToFireStore() {
// Access a Cloud Firestore instance from your Activity
final FirebaseFirestore db = FirebaseFirestore.getInstance();
/* ----------------_-------Collection delete is not supported-----------------------
----------Hence get all the document (coins) for individual Docs delete----------
*/
//------------------------------- Getting document (coins) Ends ---------------------------------------------
final List<String> coinsFromFirestore = new ArrayList<>();
db.collection("cryptos").document(userEmailID).collection("coin")
.whereEqualTo("createdBy", userEmailID)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
System.out.println("Testing 1 Batch Read done" + document.getData());
coinsFromFirestore.add(document.getData().get("coinname").toString());
}
//------------------------------- Getting document (coins) Ends ---------------------------------------------
if(coinsFromFirestore.size()>0){
for (int i=0;i<coinsFromFirestore.size();i++) {
if ( i<(coinsFromFirestore.size()-1) ) {
db.collection("cryptos").document(userEmailID).collection("coin").document(coinsFromFirestore.get(i))
.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
System.out.println("Testing 1 Successfully Deleted the document " );
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
System.out.println("Testing 1 Error Deleting the document ");
}
});
}else{
db.collection("cryptos").document(userEmailID).collection("coin").document(coinsFromFirestore.get(i))
.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
addTranToFireBaseeNow(db);
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! For last coin Ends !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
}
}else{
addTranToFireBaseeNow(db);
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
//------------------------------- Getting document (coins) Ends ---------------------------------------------
}
private void addTranToFireBaseeNow(FirebaseFirestore db) {
WriteBatch batch = db.batch();
DocumentReference newCoinRef;
//CollectionReference cryptos = db.collection("cryptos");
List<Tran> tranList = getAllTranForFireStore();
String firebaseUID = FirebaseAuth.getInstance().getCurrentUser().getUid();
for (Tran t : tranList) {
Map<String, Object> tranData = new HashMap<>();
tranData.put("firebaseid", firebaseUID);
tranData.put("createdBy", userEmailID);
tranData.put("coinid", t.getCoinID());
tranData.put("coinname", t.getCoinName());
tranData.put("coinsymbol", t.getCoinSymbol());
tranData.put("date", String.valueOf(t.getDate()));
tranData.put("qty", String.valueOf(t.getQty()));
tranData.put("price", String.valueOf(t.getPrice()));
tranData.put("priceunit", String.valueOf(t.getPriceUnit()));
newCoinRef= db.collection("cryptos").document(userEmailID).collection("coin").document(t.getCoinName());
batch.set(newCoinRef, tranData);
}
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// ...
}
});
}
No index is setup for my DB

Since you are using set without any options, it will overwrite the existing data. But your requirement is to merge your data, so you have to use the merge option as follows:
batch.set(newCoinRef, tranData, SetOptions.merge());
You can read more about options here.
Furthermore there is a good post which lists the differences between set, update and create.

You can use update and change one variable
reference.document("documentname").update("field", variable)
or
reference.document("documentname").set({
field: variable
}, { merge: true });
Or.. if you have to update an entire object, you can use:
reference.document("documentname").set(newObject, { merge: true });
Check this article:
https://saveyourtime.medium.com/firebase-cloud-firestore-add-set-update-delete-get-data-6da566513b1b

Related

How to remove two documents in Firebase cloud database?

I wrote the following code that deletes two documents from the cloud Firebase database:
fireDB.document(groupPath).collection("users").document(phoneNumber).delete().addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
fireDB.collection("users").document(phoneNumber).delete().addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(this.getClass().getName(), "DocumentSnapshot successfully deleted");
Toast.makeText(getApplicationContext(),R.string.successfully_deleted_user,Toast.LENGTH_LONG).show();
finish();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(this.getClass().getName(), "Error deleting document", e);
Toast.makeText(getApplicationContext(),R.string.failed_to_delete_user,Toast.LENGTH_LONG).show();
}
});
Log.d(this.getClass().getName(), "DocumentSnapshot successfully deleted");
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(this.getClass().getName(), "Error deleting document", e);
Toast.makeText(getApplicationContext(),R.string.failed_to_delete_user,Toast.LENGTH_SHORT).show();
}
});
The problem with that code is that it deletes first document and then deletes second document, meaning if the first try will delete it successfully and the second one will fail to delete it, there is going to be a problem. Is it possible to delete two documents in Firebase cloud database so the result could be of of the following two options:
Both of the documents are deleted.
Both of the document are not deleted.
Is it possible to do?
As robsiemb commented, you'll want to use a batch write or transaction for this.
As far as I can see, the equivalent from your code would be something like this:
// Get a new write batch
WriteBatch batch = db.batch();
DocumentReference docRef1 = fireDB.document(groupPath).collection("users").document(phoneNumber);
DocumentReference docRef2 = fireDB.collection("users").document(phoneNumber)
DocumentReference laRef = db.collection("cities").document("LA");
batch.delete(docRef1);
batch.delete(docRef2);
// Commit the batch
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
// ...
}
});

How to fetch sub-collection data from firestore

I have a collection called TripInfo following with user's id and then I have created subcollection with auto generate id for each doc. The problem is I can't fetch the data using the following code:
private void checkDataExistingDate() {
db.collection("TripsInfo").document(UID).collection("Individual_Trip").document()
.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.getResult().exists()){
DocumentSnapshot snapshot = task.getResult();
Map<String, Object> map = snapshot.getData();
for (Map.Entry<String, Object> entry : map.entrySet()) {
Log.d(Tag, "All data"+entry.getValue().toString());
}
}
}
});
Or do I need to store the without subcollection? In Realtime we were using push to generate auto-id, but here I don't know what is the equivalent to create a key after placing the user's id in the document.
Suppose you have store userid with uid parameter in TripsInfo collection and also have Individual_Trip as subcollection in 'TripsInfo' collection. you can fetch subcollection like this.
FirebaseFirestore.getInstance().collection("TripsInfo")
.get()
.addOnCompleteListener(
new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
if (FirebaseAuth.getInstance().getUid().equalsIgnoreCase(document.getString("uid"))) {
document.getReference()
.collection("Individual_Trip")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
list = task.getResult().toObjects(Individual_Trip.class);
showListOfRequest();
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
hideProgress();
}
});
break;
}
}
}
}
});
FirebaseAuth.getInstance().getUid().equalsIgnoreCase(document.getString("uid"))
this line is for checking for userid, when you have multiple data in your 'TripsInfo' collection.
you can also use whereEqualTo for that, FirebaseFirestore.getInstance().collection("TripsInfo")
.whereEqualTo("userId", "uid").get()...

ArrayList<String> method return an empty array getting data from firebase

I have a problem with my return..since the data required a few second to get downloaded, when the method return the ArrayList, it's still empty. In fact if I put that lines of code Log.v("array", String.valueOf(partecipantsArrayList));
first it print 0 and then it print the array filled. So I have to wait before the return statement..is there any way to achieve this?
public ArrayList<String> getPartecipantsList(){
String email = getEmail();
String groupTitle = getTitleBar();
DocumentReference docRef = db.collection("users").document(email).collection("Group").document(groupTitle);
docRef.get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
DocumentSnapshot document = task.getResult();
//Extracting participants ArrayList from the document
for(Object item : task.getResult().getData().values()) {
String[] values = String.valueOf(item).replace("[", "").replace("]", "").split(",");
for (String value : values){
partecipantsArrayList.add(value);
}
}
partecipantsArrayList.remove(String.valueOf("["));
partecipantsArrayList.remove(partecipantsArrayList.size() - 1);
Log.v("array", String.valueOf(partecipantsArrayList));
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
return partecipantsArrayList;
}
It is async task you can adjust this code like below.
change the signature of that method to return void
public void getPartecipantsList();
do next action after the on complete
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
...............
partecipantsArrayList.remove(String.valueOf("["));
partecipantsArrayList.remove(partecipantsArrayList.size() - 1);
Log.v("array", String.valueOf(partecipantsArrayList));
// do your action by calling next method from here
//eg: setResultFromArray(partecipantsArrayList);
}
do fail action or call method when failures in side the onFailure.
#Override
public void onFailure(#NonNull Exception e) {
//......
}

Cloud Firestore Update

I'd like update document in cloud firestore according to value in document. Something like this. I have few documents with random names and values and one document with values id = 1 , name = patryk . And now I'd to update document with name=patryk. And I don't know document name because I make them like this and they have a random name.
b.collection("Users")
.add(postMapa)
.addOnCompleteListener
How to do this? here I need have the name of the document but I don't have.
Try this and make sure there will be only single document for name = patryk
db.collection("Users").whereEqualTo("name", "patryk").addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
if (e == null) {
String documentId = queryDocumentSnapshots.getDocuments().get(0).getId();
// here you have id but make sure you have only one document for name=patryk
}
}
});
Assuming that the id property is of type number and the name property is of type String, please use the following code in order to update all users with the id = 1 and name = patryk:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
Query query = rootRef.collection("Users").whereEqualTo("id", 1).whereEqualTo("name", "patryk");
query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<String> list = new ArrayList<>();
for (DocumentSnapshot document : task.getResult()) {
list.add(document.getId());
}
for (String id : list) {
rootRef.collection("Users").document(id).update("name", "New Name").addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Name Updated!");
}
});
}
}
}
});
If you are using a model class for your user, please see my answer from this post.
you can retrieve documents by their name (which does not really apply here):
DocumentReference docRef = db.collection("Users").document("patryk");
or you can query for values contained in a document (which might answer your question):
/* create a reference to the Users collection */
CollectionReference users = db.collection("Users");
/* create a query against the collection */
Query query = users.whereEqualTo("name", "patryk");
the rest is the same procedure, in both cases:
/* asynchronously retrieve the document */
ApiFuture<DocumentSnapshot> future = docRef.get();
/* future.get() blocks on response */
DocumentSnapshot document = future.get();
if (document.exists()) {
System.out.println("Document data: " + document.getData());
} else {
System.out.println("No such document!");
}
Simple way to get all document id under the collections:
firebase.collection("Users").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<String> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
list.add(document.getId());
}
Log.d(TAG, list.toString());
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});

Firestore transactions conditional update?

I'm trying to check to see if a value saved in the database matches the user selection, if it does then update it otherwise don't do anything.
I've tried the following, but get an exception:
Caused by: com.google.firebase.firestore.FirebaseFirestoreException: Every document read in a transaction must also be written.
Here's my code:
//update toolbar titles if they match
final DocumentReference adminDocRef = mDbase.collection("admins").document(adminID);
mDbase.runTransaction(new Transaction.Function<Void>() {
#Override
public Void apply(#NonNull Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot adminSnapshot = transaction.get(adminDocRef);
String toolbarTitle = adminSnapshot.getString("displayedUser");
if (userName.equals(toolbarTitle)) {
transaction.update(adminDocRef, "displayedUser", userName);
}
// Success
return null;
}
})
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Transaction success!");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Transaction failure.", e);
}
});
Are transactions capable of being conditional? If not how would you solve this problem? Seems silly to have to do a separate get then nest an update inside of it.
Any help would be greatly appreciated.
Thanks!
There is a similar answer from Alex ,from documentation
A transaction can fail for the following reasons:
The transaction contains read operations after write operations. Read operations must always come before any write operations. //not your case
The transaction read a document that was modified outside of the transaction. In this case, the transaction automatically runs again. The transaction is retried a finite number of times.
In your case I would try
if (userName.equals(toolbarTitle)) {
//transaction operations
}
See if that works!
So in order to update my toolbarTitle conditionally, I would have to cascade my Firestore operations this way:
DocumentReference adminDocRef = mDbase.collection("admins").document(adminID);
adminDocRef.get().addOnCompleteListener(activity, new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
//assign document value to toolbarTitle
toolbarTitle = document.getString("displayedUser");
if (userName.equals(toolbarTitle)) {
DocumentReference adminDocRef = mDbase.collection("admins").document(adminID);
adminDocRef
.update("displayedUser", toolbarTitle)
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Displayed child has been updated");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Error: updating displayed child", e);
}
});
Log.d(TAG, "DocumentSnapshot data: " + document.getData());
}
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
It's a bit disappointing that I would have to do it this way, as it seems having it wrapped inside of a transaction would make more sense. I'm hoping that they allow conditional behavior inside the Transaction.
Actually, google has a class called Tasks in which you could use to make firestore calls synchronous.
So following your example, you could fix it into something like:
try {
DocumentSnapshot adminDocument = Tasks.await(adminDocRef.get());
if (adminDocument.exists()) {
String toolbarTitle = adminDocument.getString("displayedUser");
if (userName.equals(toolbarTitle)) {
Tasks.await(adminDocRef.update("displayedUser", userName));
//Success
}
}
} catch (ExecutionException e) {
//Error
e.printStackTrace();
} catch (InterruptedException e) {
//Error
e.printStackTrace();
}
This way you don't need listeners and at the same time it is a conditional update. I know this is late but hopefully it'll help someone.
[UPDATE]:
Please note that Tasks.await(...) call should not be called on the main thread, a solution would be using AsyncTask.

Categories

Resources