update single document field in firestore - android

I have a document ref user/userid23435534 and I want to update a single (document has several fields) field(nick) in that document.
I call this method: ref.ref.update("nick","test123") and I can see from logging that ref.getpath() is user/userid23435534 indeed.
However, after calling this method, and getting success from my OnSuccessListener I still see that my field is not updated in the firestore database. What did I get wrong here?
EDIT:
public static void updateDocument(){
final DocumentReference ref = db.collection("user").document("userid23435534");
ref.update("nick", "test123" ) //logging shows that red.getPath is "user/userid23435534"
.addOnSuccessListener(aVoid -> {
//success is called when calling method that runs this code
}).addOnFailureListener(e -> {
//....
})
}

I just ran this code in my local emulator, and it updates the document without problems:
DocumentReference ref = db.collection("56246892").document("uid");
ref.update("nick", "test123" ).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
System.out.println("Updated");
}
});
Are you sure the document already exists? That is required for update() to work. If you're not sure whether the document exists, use set(..., SetOptions.merge()).

Related

FirebaseFirestore add() method being executed before validation (get() documents) [duplicate]

This question already has answers here:
How to return a DocumentSnapShot as a result of a method?
(2 answers)
Closed 2 years ago.
I'm building an APP and using Firestore to create a collections of Users. Before adding the new user to the collection i need to check within the collection if the email is already in use and for this i've built two methods: one for reading the collection looking for an user with that email and the other one to adding the new user IF everything is ok. But no matter what I do, the add method always executes first leading to the validation being useless. I guess it's has something to do with the methods priority withing Firebase but i really couldn't pull out with a solution
Here's the two methods
The first one it's validation and the second one it's the add
private boolean createFirestoreUser(final String identificador) {
final boolean[] isValid = {true};
FirebaseFirestore.getInstance().collection("Usuarios")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
for (QueryDocumentSnapshot document : task.getResult()) {
if(identificador.equals(document.getString("identificador")))
isValid[0] = false;
}
}
});
return isValid[0];
}
private void createUser(Usuario novoUsuario) {
FirebaseFirestore.getInstance().collection("Usuarios")
.add(novoUsuario)
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
#Override
public void onSuccess(final DocumentReference documentReference) {
documentReference
.update("id", documentReference.getId())
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
loginSessionManager.createLoginSession(documentReference.getId());
loginSessionManager.checkLogin(this.getClass());
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
snackbar.showMensagemLonga(v,e.getMessage());
}
});
}
The second one is ALWAYS being called first no matter the order i use on the button listener. I've debbuged and it really enters in the isValid[0] = false after the user is added
Where are you calling the methods?
You could just call the createUser inside of the .addOnSuccessListener this way it will not be called until the valdiation is returned.
Something like:
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
for (QueryDocumentSnapshot document : task.getResult()) {
if(identificador.equals(document.getString("identificador")))
isValid[0] = false;
else
createUser(novoUsuario)
}
}
The reason why I'm asking where you are calling the methods is because your variable might be by default true which would trigger the second function before the async listener is returned therefore calling the second method before the validation is made.
So the alternative would be to call the register method inside the same mehrod where you are validating or if what I'm assuming that you have a boolean declared first to see if you call the create method, just have it false as default and make sure to be calling it after the async .OnCompleteLister is finished.

How to add new fields in Firestore Document from two different sources simultaneously?

When a new user registers in my app using firebase-authentication custom sign in using email and password, I need to update that data into my firestore.
But Firebase only has FirebaseAuth.createUserWithEmailAndPassword(email, password) to create a new account and hence I cannot update my user's username at the same time.
To update the E-Mail in Firestore, I use Firebase cloud functions. Here's the code:
export const onNewUserJoined = functions.auth.user().onCreate((user) => {
//const newUserDisplayName = user.displayName //CAN'T USE THIS. REASON is BELOW
const newUserUID = user.uid
const newUserEmail = user.email
const timeCreated = Date.now()
console.log(`${newUserUID} has joined.`)
return admin.firestore().collection('Agent').doc(`${newUserUID}`).set({"E-Mail": newUserEmail, "Time": timeCreated})
})
OK, great now I have updated the E-Mail and time created in Firestore successfully.
But next challenge is I need to update the user's username in the same Firestore document. I do it instantly after the createUserWithEmailAndPassword() like this:
DocumentReference dDocRef = FirebaseFirestore.getInstance().document(documentPath);
Map<String, Object> updateUsernameAndPhone = new HashMap<>();
updateUsernameAndPhone.put("username", username);
updateUsernameAndPhone.put("phoneData", phoneModel);
dDocRef.update(updateUsernameAndPhone).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(getApplicationContext(), "Data successfully stored in Firestore", Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
}
});
Now, it depends on who acts first, the cloud function or the user's device.
If the cloud functions act first, then there is no issue. The username and phoneModel both get updated into the document successfully. No issues.
But incase, the phone acts first then I get the following error:
As this error has occurred, username isn't in the document and only email and timeCreated are in the document updated by the cloud function which got late to create document so that user's device can update the username with ease.
I CAN'T use .SET instead of .update() in my app because if I use .set() and the cloud functions create the email and timeCreated fields first. Then the device will DELETE them and put username and phoneModel.
So how can I do this?
I can forcefully delay updating the username by putting it in the next activity so that cloud functions get enough time to do their job, but my signUpActivity asks for username along with email and password edit texts. I don't want to create a separate activity for that.
I used to use .update() when my data was stored in realtime database and it used to create the child even if the path didn't exist. But it looks firestore won't update if the field doesn't exist.
Any solution for this?
I tried as per #DougStevenson said and here's my code:
final String newUserUID = Objects.requireNonNull(signUpAuth.getCurrentUser()).getUid();
final String documentPath = "Agent/" + newUserUID;
FirebaseFirestore fFirestoreRef = FirebaseFirestore.getInstance();
final DocumentReference dDocRef = fFirestoreRef.document(documentPath);
fFirestoreRef.runTransaction(new Transaction.Function<Void>() {
#Nullable
#Override
public Void apply(#NonNull Transaction transaction) throws
FirebaseFirestoreException {
DocumentSnapshot documentSnapshot = transaction.get(dDocRef);
transaction.update(dDocRef, "username", username);
transaction.update(dDocRef, "phoneData", phoneModel);
return null;
}
}).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(getApplicationContext(), "Data updated in Firestore . . .", Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
}
});
But no luck. It still gives me error: Cannot update a document which does not exist.
Use a transaction to resolve conflicts from multiple clients that are all trying to modify the same document. The transaction handler will be retried on the client if it detects the document was modified before the change could take place.

Multiply reading of document references from Firestore using tasks

I have a Map of references which I read from Firestore. these refs lead me to documents that I'm willing to use their data to create an instance of my class 'Contact'.
In order to do that I've created a list of tasks which every task of it uses its ref to read from Firestore and retrieve the needed data.
Once it's all done I use Tasks.whenAll(tasks).addOnSuccessListener() willing to retrive my new array of Contacts.
On this method, 'contacts' is empty and 'data' is full of document references.
I expected Tasks.whenAll(tasks) to being called only when all this reading using the refs has completed, however it's being called immediately, therefore - nothing happens.
private void createContactArray(final ArrayList<Contact> contacts, final Map<String, DocumentReference> data) {
List<Task<DocumentSnapshot>> tasks = new ArrayList<>();
for (final Map.Entry<String, DocumentReference> entry : data.entrySet()) {
tasks.add(db.document(entry.getValue().getPath()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Map<String,String> contactDetails = (Map<String, String>) document.getData().get(entry.getKey());
Contact contact = createContact(contactDetails);
if(contact != null){ contacts.add(contact);}
} else {
Log.d(ACTION_FETCH_CONTACT_LIST,"There was ref problem with " + entry.getKey());
}
}else {
Log.d(ACTION_FETCH_CONTACT_LIST, "get failed with ", task.getException());
}
}
}));
}
Tasks.whenAll(tasks).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
sendBroadcastActionContactList(contacts);
}
});
I would like Tasks.whenAll to be called once its all finished and not right away. I wish to have a proper explanation for the issue and a decent code that should do the job instead of mine.
I really appreciate your help!
You are using the APIs incorrectly. You should be collecting tasks returned by get() into an array, instead of immediately adding a callback to each one. Pass that list of tasks to Tasks.whenAll(). Then, in the callback for the task returned by Tasks.whenAll, you can examine each DocumentSnapshot results.

firestore onSuccess listener isn't working

I am trying to download some Quiz objects from my database.
The following function is called from onCreate of a certain activity.
private void downloadQuizzesFromCloud(){
String user_id = FirebaseAuth.getInstance().getCurrentUser().getUid();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
String user_quizzes_path = "users/".concat(user_id).concat("/quizzes");
Query userQuizzes = db.collection(user_quizzes_path);
userQuizzes.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
quizzes.clear();
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
Toast.makeText(QuizzesActivity.this,"downloaded to list ".concat(String.valueOf(quizzes.size()).concat(" quizzes")), Toast.LENGTH_SHORT).show();
//TODO put in recycle adapter
} else { }
}
});
}
(user_quizzes_path contains the correct path to a collection of Quiz objects stored on the cloud)
I debugged this functions and found out that after the command:
userQuizzes.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>()
The function finishes execution, that is the onComplete cases aren't checked and executed and all this code is just skipped.
I tried to find this on the documentation of firebase but didn't find anything.
Why is this happening and how can I fix this?
Would appreciate some help here, thanks!
The onComplete is called when the read operation has completed from the Firestore servers. If it's not getting called, I can see two possible reasons:
You're not connected to the server. Unless you've read the data before (and it's in the local database that the Firestore client maintains), this means the read never completes locally.
You're not thinking asynchronously. Note that data is read from the server asynchronously, and there may be some time between when you call get() and when onComplete fires. To test if this is the case, put a breakpoint on if (task.isSuccessful()) { and run the app in the debugger. The breakpoint will hit when the data is read from the server.
Use a callback interface. Just like this below.
private void downloadQuizzesFromCloud(Consumer listener) {
String user_id = FirebaseAuth.getInstance().getCurrentUser().getUid();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
String user_quizzes_path = "users/".concat(user_id).concat("/quizzes");
Query userQuizzes = db.collection(user_quizzes_path);
userQuizzes.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<Quiz> quizzes = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
listener.onGet(quizzes);
Toast.makeText(QuizzesActivity.this,"downloaded to list ".concat(String.valueOf(quizzes.size()).concat(" quizzes")), Toast.LENGTH_SHORT).show();
//TODO put in recycle adapter
} else { }
}
});
}
interface Consumer {
void onGet(List<Quiz> quizzes);
}

Delete data from firestore

I have add data on firestore Firebase but I don't have specific document because I use addSnapShotListener to retrieve the data. How can I delete the document when I don't know the name of it. Here is the code where I add the data:
mondayCollectionReference.document().set(userMap, SetOptions.merge()).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Toast.makeText(TimeForm.this, "Submitted", Toast.LENGTH_SHORT).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d("ERROR", e.getMessage());
}
});
document() returns a DocumentReference object. That object gives you everything you need to know to delete it, especially its own delete() method. It also has a getId() method to help you remember its id.
So, you should store the DocumentReference object first before calling methods on it:
DocumentReference ref = mondayCollectionReference.document()
String id = ref.getId();
ref.set(...);
// use ref or id later if you want to delete it

Categories

Resources