Android - How to handle Firestore exceptions at user level? - java

I'm using Firestore in my app and I can't figure out how to handle at user level the exceptions thrown by that. (I mean what to display to the user when such exceptions occur).
For example, to perform any CRUD operation on Firestore (DocumentReference#get, DocumentReference#set, DocumentReference#update) a Task is returned, which might contain an exception, but in the documentation I can't find why this exception might be thrown by Firestore.
Is there something better that we can do, rather than simply log the exception and show a generic message like "an error occurred, please try again later"?

As in the official documentation regarding getting data, you can get the exception from the task object like this:
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Log.d(TAG, "DocumentSnapshot data: " + document.getData());
} else {
Log.d(TAG, "No such document");
}
} else {
//Log the error if the task is not successful
Log.d(TAG, "get failed with ", task.getException());
}
}
});
And remember, a Task is complete when the work represented by the Task is finished, regardless of its success or failure. There may or may not have been an error, and you have to check for that. On the orter side, a Task is "successful" when the work represented by the task is finished, as expected, with no errors.
As #Raj mentioned in his answer, you can also use addOnFailureListener but note, if there is a loss of network connectivity (there is no network connection on user device), neither onSuccess() nor onFailure() are triggered. This behavior makes sense, since the task is only considered completed when the data has been committed (or rejected) on the Firebase server. onComplete(Task<T> task) method is called also only when the Task completes. So in case of no internet connection, neither onComplete is triggered.

You can use onFailureListener() method of Firestore and get the errors while getting, setting or updating the data. In this example I have used it in setting data:-
firestore.collection("User").document(uid).set(user).addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid)
{
//Data Saved Successfully
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(Exception e)
{
//Toast error using method -> e.getMessage()
}
});
If you want to catch exceptions in firebase authentication module then refer:- How to catch a Firebase Auth specific exceptions

Related

Faced an Issue related to Firebase Cloud Firestore

I was trying to store data to Firebase Cloud Firestore but after running my app it showed this error in the log.
[Firestore]: Write failed at Backpaper Registration/WSjZoirLf8WRBuGCVbUZ: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
This is the part of my code.
fb=FirebaseFirestore.getInstance();
map=new HashMap<String>();
map.put("Name of Department",txtDepartment);
map.put("Name of Student",txtStudentName);
map.put("Registration No",txtRegNo);
map.put("CGPA in Previous Semester",txtCgpa);
map.put("Phone",txtPhoneNo);
map.put("Email",txtEMail);
map.put("Subject 1",txtSubject1);
map.put("Subject 2",txtSubject2);
map.put("Subject 3",txtSubject3);
map.put("Subject 4",txtSubject4);
map.put("Subject 5",txtSubject5);
map.put("Payment Details(Rs.)",txtPaymentDetails);
map.put("Payment Date",txtPaymentDate);
fb.collection("Backpaper Registration").add(map).addOnCompleteListener(new OnCompleteListener<DocumentReference>() {
#Override
public void onComplete(#NonNull Task<DocumentReference> task) {
if(task.isSuccessful()){
Toast.makeText(Backpaper.this, "Registration Successful", Toast.LENGTH_SHORT).show();
startActivity(new Intent(Backpaper.this,Academic.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP));
finish();
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(Backpaper.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
Can somebody help me?
You should check out the Rules on Firebase
Change allow read, write: if false; to true;
Nots:
It is not advised to choose this solution because it makes the
database unsafe.
Only use this for testing purposes.
You need the Firebase rules setting at Firebase console.
If you had signed into project, the rules setting may like :
// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
If you just demo the function, you call setting the rules:
// Allow read/write access to all users under any conditions
// Warning: **NEVER** use this rule set in production; it allows
// anyone to overwrite your entire database.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
The more officially document you can found here.
https://firebase.google.com/docs/firestore/security/get-started
It could be because, you have not given the read & write permission to firebase.
Change your Firebase Database to test mode.
1. Go to your firebase database console
2. Go to Rules
3. Start in test mode
This will change allow read, write: true
This turn off the database security but for testing purpose it is advised

How Should I fetch the document fields and use them in another map for another collection?

How should I fetch the document fields from one collection and combine them to add a new document to another collection? I have attached picture of the database how does it looks, I want to fetch the fields from the collection show and want to update it to the new collection along with some other data:
private void savePost(String mPostTitle, String mPostContent, String mlistSpinnerC) {
final DocumentReference docRef = FirebaseFirestore.getInstance().collection("users").document(mauth.getCurrentUser().getUid());
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document != null) {
String username = (String)
document.get("username");
String email= (String) document.get(email);
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
postMap.put(Constants.POSTTTITLE, mPostTitle);
postMap.put(Constants.POSTCATEGORY, mlistSpinnerC);
postMap.put(Constants.POSTCONTENT, mPostContent);
postMap.put(Constants.TIMESTAMP, (System.currentTimeMillis()/1000));
postMap.put(Constants.USER_ID,mauth.getCurrentUser().getUid());
postMap.put("username", username);
PostsRef.document().set(postMap).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
Intent toHomeActivity = new Intent(AddPostActivity.this, MainActivity.class);
startActivity(toHomeActivity);
}
}
});
I am just not able to map the fields from one collection to another collection, please guide me the correct method to that.
By the time you are trying to add the username to your postMap using the following line of code:
postMap.put("username", username);
The data has not finished loading yet from the database and this is because the listener you have added to your get() call is being invoked some unknown amount of time later after your query finishes. You don't know how long it's going to take, it may take from a few hundred milliseconds to a few seconds before that data is available. The onComplete() method has an asynchronous behavior, that's why you cannot get that username in such a way.
A quick solve for this problem would be to move all that block of code related to adding data to the postMap, inside the onComplete() method. In this you are waiting for the callback and username your will be available. Otherwise I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

Why does the command transaction.update is executed before the carrelloAttuale.prodotti.add() command

I'm trying to get a product from a document form the cloud firestore and then put that product in the shopping cart. When i read (successfully) the product, i try to put it in an arraylist that is declared outside but it doesnt work unless i put final to the variable.
Doing so, when I run the code below, I successfully retrieve the data, but the operation carrelloAttuale.prodotti.add(prod) is executed after the command transaction.update(), so the update doesn't upload nothing different from the start.
//prendo l'utente
FirebaseAuth auth= FirebaseAuth.getInstance();
//mi salvo il codice del prodotto scannerizzato
final String codiceProdottoScannerizzato=String.valueOf(intentData);
final FirebaseFirestore db = FirebaseFirestore.getInstance();
final DocumentReference docRef = db.collection("carrelli").document(auth.getUid());
final DocumentReference docrefprodotti = db.collection("prodotti").document(codiceProdottoScannerizzato);
db.runTransaction(new Transaction.Function<Void>() {
#Override
public Void apply(Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot snapshot = transaction.get(docRef);
final Carrello carrelloAttuale = snapshot.toObject(Carrello.class);
docrefprodotti.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Prodotti prod=document.toObject(Prodotti.class);
prod.id=codiceProdottoScannerizzato;
prod.totalePezziCarrello=1;
carrelloAttuale.prodotti.add(prod);
Log.d(TAG, "PRODOTTO: " + prod.toString());
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
Log.d(TAG, "CARRELLO FB: " + carrelloAttuale.size());
transaction.update(docRef, "prodotti", carrelloAttuale.getProdotti());
// 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);
}
});
I expect that the command update is executed after the carrelloAttuale.prodotti.add(prod)
in the debug log the order of tags are:
CARRELLO FB: 0
PRODOTTO: Nome: latte
Data is loaded from Firestore asynchronously, since it may have to be retrieved from the server. To prevent blocking the app, the main code continues while the data is being retrieved. Then when the data is available, your onComplete gets called.
This means that any code that needs the data from the data, must be inside the onComplete method, or be called from there. So something like:
docrefprodotti.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Prodotti prod=document.toObject(Prodotti.class);
prod.id=codiceProdottoScannerizzato;
prod.totalePezziCarrello=1;
carrelloAttuale.prodotti.add(prod);
Log.d(TAG, "PRODOTTO: " + prod.toString());
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
Log.d(TAG, "CARRELLO FB: " + carrelloAttuale.size());
transaction.update(docRef, "prodotti", carrelloAttuale.getProdotti());
}
});
Also see:
How to return a DocumentSnapShot as a result of a method?
Firebase Firestore get data from collection
"the command update" is executed before "carrelloAttuale.prodotti.add(prod)" is called because the onComplete() method has an asynchronous behaviour and returns immediately. This means that listener will not get invoked until some time later, after the database update operation is complete. There is no guarantee how long it will take. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds for the update operation to complete.
If you want to use some logic with that data, you must wait until the asynchronous Firebase database operation is complete. This means that you can only use the prod object inside the listener callback itself.
For more informarions, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

Method returning value before code can run to update the value

In the below example, I call a method signinUser(username, password) This then runs through Firebase to determine if the user has successfully or unsuccessfully been able to sign in. However, it takes Firebase a short amount of time to do this, which by that time the method has already returned with the original value, before being updated by the successful-ness / unsuccessful-ness of the sign in process.
How would I go about returning the method once the Firebase authentication has done it's thing. I am aware I could put a timer on when the return statement is called, however, I'm not sure how that'd work as slow internet connects could cause it to take longer than the given amount set by a timer.
My code is as follows:
public AuthSuccess signinUser(String username, String password) {
mAuth.signInWithEmailAndPassword(username + "#debugrestaurant.com", password)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Toast.makeText(context, "Successful!", Toast.LENGTH_SHORT).show();
authSuccess = new AuthSuccess(true, null);
} else {
Toast.makeText(context, "Unsuccessful!", Toast.LENGTH_SHORT).show();
authSuccess = new AuthSuccess(false, task.getException());
Toast.makeText(context, "Exception: " + authSuccess.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
}
});
return authSuccess;
}
Please note that the class AuthSuccess is simply an object I've created to collect whether or not the sign in was successful, and if not, to collect the exception.
You cannot return something now that hasn't been loaded yet. With other words, you cannot simply use the authSuccess object outside the onComplete() method because it will always be null due the asynchronous behaviour of this method. This means that by the time you are trying to return that result outside that method, the data hasn't finished loading yet from the database and that's why is not accessible. A quick solve for this problem would be to use the value of authSuccess only inside the onComplete() method, otherwise I recommend you see the last part of my anwser from this post in which I have exaplined how it can be done using a custom callback. You can also take a look at this video for a better understanding.
Your old code is probably something like this
public void onSignInClick(){
AuthSuccess result = signinUser(username, password);
if(result.success){
startMainActivity();
}else{
showErrorPopUp();
}
}
Change this to:
public void onSignInClick(){
signinUser(username, password);
}
protected void handleLoginResult(AuthSuccess result){
if(result.success){
startMainActivity();
}else{
showErrorPopUp();
}
}
And at the very end of your onComplete method do:
handleLoginResult(authSuccess);

Promises in Java

The issue I am ultimately trying to solve, before I pose my question, is the synchronicity of querying my Firebase database and writing code based on the result. A simple example to illustrate:
Boolean userFound = false;
DatabaseReference userName = FirebaseDatabase.getInstance().getReference().child("Profiles").child("Name");
userName.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String name = dataSnapshot.getValue().toString();
userFound = true;
Toast.makeText(getBaseContext(), "Welcome back, " + name + ".", Toast.LENGTH_SHORT).show();
}
#Override
public void onCancelled(DatabaseError databaseError) {
//Never used this section
}
});
If (!userFound) {
Toast.makeText(getBaseContext(), "User not found.", Toast.LENGTH_SHORT).show();
}
In the example above, the listener looks for a name in the database. If the name is found it gives a welcome message and sets "userFound" to true. If a name is not found, "userFound" will remain as false and you can generate a user not found message.
The problem with this is that everything runs at the same instant and so you will always get the "User not found" message instantly, and then a few seconds later the listener might actually find the user and say "Welcome back".
I have been looking into how I can possible resolve this, and I have found Java Promises. Am I looking in the right direction? Here are two promise examples:
CompletableFuture.supplyAsync(this::failingMsg)
.exceptionally(ex -> new Result(Status.FAILED))
.thenAccept(this::notify);
This code looks great, and the article here is very detailed in its usage: http://www.deadcoderising.com/java8-writing-asynchronous-code-with-completablefuture/
Except for the fact that is will ONLY work in API 24 and above. Which means your app will not work on 90% of devices. So this is essentially worthless.
The other way of doing this is as follows:
try {
Promise { client.newCall(request).execute() }
.then { ... }
.thenAsync { ... }
.then { ... }
} catch (e: Exception) {
...
}
As explained here: https://medium.com/#sampsonjoliver/promises-in-android-and-java-d6b1c418ea6c
Except that when I try to use this code there is no such thing as Promise. It just says it cannot resolve the symbol. So this guy has written an article on something that doesn't even exist.
Am I looking at the right stuff here? The end game is to make my app wait for the result of any database lookup before continuing to process code. If I cannot do this, then the database becomes completely useless.
Thanks guys. Please help!
The solution with a problem using asynchronous APIs is pretty much always the same: move the code that needs access to the data into the method that is called when the data is available.
So in your case that means moving the check and toast into onDataChange:
DatabaseReference userName = FirebaseDatabase.getInstance().getReference().child("Profiles").child("Name");
userName.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Boolean userFound = false;
String name = dataSnapshot.getValue().toString();
userFound = true;
Toast.makeText(getBaseContext(), "Welcome back, " + name + ".", Toast.LENGTH_SHORT).show();
if (!userFound) {
Toast.makeText(getBaseContext(), "User not found.", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
});
For more on this, see:
Setting Singleton property value in Firebase Listener
Doug's excellent blog post
Querying data from firebase
Wait Firebase async retrive data in android
Handle data returned by an Async task (Firebase)

Categories

Resources