I'm storing structured data on Firestore and I want to get the data of a specific nested document and it must be in real time because the data is updating in real time.
Here is the structure in Firestore:
So basically, I have the following path :
collection(Users).document(23).collection(orders).document(16)
or
/Users/23/orders/21
..and I want to get the data of document 16 of the "orders" collection.
Here's the code
class FireStoreMapActivity{
DocumentReference doc;
void getData(){
doc = FirebaseFirestore.getInstance()
.collection("Users")
.document(23 + "")
.collection("orders")
.document(21 + "");
doc.addSnapshotListener(this, (documentSnapshot, e) -> {
if (documentSnapshot != null) {
if (documentSnapshot.exists()) {
List<String> images = (List<String>) documentSnapshot.get("images");
Log.e(">>>>>>>>>>> size => ", locHistory.size() + " ");
} else {
Log.e(">>>>>>>> error ", " documentSnapshot != exists");
}
} else {
Log.e(">>>>>>>> error ", " documentSnapshot = null");
}
});
}
}
But every time, the null log appear
Log.e(">>>>>>>> error ", " documentSnapshot = null");
UPDATE
well, after printing the exception it seems to be a permission error!
com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.
Here's my ruleset
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
but how to inform Firestore that I'm in fact an authenticated user.!?
If documentSnapshot is null and you're sure the document exists, then e will have a value. Print it to see what the problem is, which typically will be something about permissions.
From your update it seems your rules requires just an authenticated user, but your listener is rejected by the server based on those rules. This means you're not signed in, or at least not signed in when you try to read from Firestore.
Given that the code you shared shows nothing about authentication, it's hard to say more. If you'd like to verify though, log the current user just before you call addSnapshotListener with something like Log.i("User", FirebaseAuth.getInstance().getCurrentUser()).
Related
I am grabbing the android device token and saving it to firestore. This works in most of the cases totally fine. Now I encountered a device where it does not work. Oddly enough I end up with attributes in firestore that my model does not reflect and I do not understand where those attributes and their values come from.
This is my method to retrieve the token:
public static void updateToken(String userId, FcmToken.Action action) {
if (StringUtils.checkNull(userId)) return;
//FCM
FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.w(TAG, "Could not get firebase token. ", task.getException());
return;
}
String token = task.getResult();
Log.i(TAG, "[FCM TOKEN]: " + token);
//Do not ask firebase to write an existing token again,
//this only creates traffic and costs but does not change the database
String storedToken = FcmProvider.loadToken(false);
if (StringUtils.checkNull(storedToken) || !token.equals(storedToken) || action == FcmToken.Action.DELETE) {
FcmToken fcmToken = new FcmToken(token, userId, action);
FcmRepo.getInstance().setToken(fcmToken, documentId -> {
Log.d(TAG, "Token " + action + " successfully");
FcmProvider.saveToken(token);
}, e -> {
Log.d(TAG, "Failed to " + action + " token. " + e);
});
} else {
Log.d(TAG, "Stored token is identical. No update needed");
}
});
}
This is the FcmToken class (And I assume the way I use the field values here are the issue, yet I do not understand the outcome)
#Getter
#Setter
#IgnoreExtraProperties
public class FcmToken {
private String userId;
private FieldValue fcmToken;
#ServerTimestamp
private Date ut;
public enum Action {SET, DELETE}
public FcmToken(String fcmToken, String userId, Action action) {
this.userId = userId;
if (action == Action.SET) this.fcmToken = FieldValue.arrayUnion(fcmToken);
else if (action == Action.DELETE) this.fcmToken = FieldValue.arrayRemove(fcmToken);
}
}
For completness this is the method I use to set the token:
public void setToken(#NonNull FcmToken fcmToken, #Nullable OnSetSuccessListener callbackSuccess, #Nullable OnFailureListener callbackFailure) {
getFcmRef().document(fcmToken.getUserId()).set(fcmToken, SetOptions.merge()).addOnCompleteListener(task ->...);
}
Now this is what ends up in my firestore for that user only:
This HAS to to come from the app itself. There is no other way this document could have been created:
If I use another device or the emulator the result looks like expected. Where does the "a" field come from? Why is it assigned to userId and why is there no fcmToken field?
The problem is, that I do not have physical access to the phone that produces this behaviour (Samsung S21 Ultra, Android 12). I need to debug this without having access to the log outputs.
But I know that the task comes back successfully (I used another version to save the error in the token field and there was none).
Any ideas?
This typically means that you didn't configure ProGuard correctly, and it's minifying the classes that you're writing to the database.
You'll need to prevent ProGuard from modifying the classes that you use to interact with the database. See the Firebase documentation on configuring ProGuard (the link is for the Realtime Database, but the same applies to Firestore), or one of these previous questions about configuring ProGuard for Firebase.
I wrote the rules for my firestore database that is only authenticated users can read and write the data into documents. The rule is:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to access documents in the "cities" collection
// only if they are authenticated.
match /PhoneAuthUsers/{phoneid=**} {
allow read, write: if request.auth.uid != null;
}
}
}
and i published the rule whenever i run the app i got the error line
"Missing Insufficient Permissions"
My authentication method is Phoneauthentication and i have the auth id in authentication tab. This is my db structure :
**- PhoneAuthUsers(Collection)
- Phonenumber(documents)
- Accounts(Collection)
-autoid(Document)**
Client side code :
db.collection("PhoneAuthUsers")
.whereEqualTo("PhoneNumber", mobile)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
System.out.println("hello user");
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData());
AuthID = document.getId();
System.out.print("AuthIDPhonenumber" + AuthID);
System.out.println("PhoneNumber");
String phone = document.getString("PhoneNumber");
Log.d(TAG, "onSuccess: " + phone);
if (phone.equals(mobile)) {
count = count + 1;
System.out.println("count" + count);
}
}
When trying to execute the query it shows missing insufficient permissions.
For example :
PhoneAuthUsers/+911234567890/Accounts/autoid/Field Values. when getting the documents from the phoneauthusers i am getting the missing insufficient permissions. How to solve the issue.
I am using Cloud Firestore in my Android app. It's a quiz application where I randomly get documents from Firestore. When the internet connection is good, the app works fine. When the network gets disconnected and then again gets connected, I am unable to read the documents. When I debug, I find that my get() method is not getting executed at all.
Iterator iterator = randomIds.iterator();
while (iterator.hasNext()) {
String documentId = (String) iterator.next();
DocumentReference documentReference = db.collection(categoryName).document(documentId);
if (documentReference!=null) {
documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
System.out.println("Task successful");
DocumentSnapshot document = task.getResult();
questionDetailsObj = new QuestionDetails();
questionDetailsObj = document.toObject(QuestionDetails.class);
if (questionDetailsObj != null) {
System.out.println("Question details: " + questionDetailsObj.getQuestion_text());
// Adding the questions to a list
questionsList.add(questionDetailsObj);
}
} else {
Log.d("MainActivity", "get() failed with " + task.getException());
}
}
});
}
}
I want to retrieve 10 documents. Sometimes, few documents are retrieved successfully and for the others I get the exception
get() failed with com.google.firebase.firestore.FirebaseFirestoreException:
Failed to get document because the client is offline.
I don't understand why would some documents come successfully and some fail to get retrieved. Please help me understand if any code changes are required.
In my case, Firestore database looks like below, where each Android client will have one document mapped to his/her device Document_sdfljkhsdio ( specific to Android User 1 )
FireStoreCollection
Document_sdfljkhsdio ( specific to Android User 1 )
Collection_xyz
Document_xyz
Document_kjjkssefd ( specific to Android User 2 )
Collection_xyz
Document_xyz
Document_sqdfwdfsme ( specific to Android User 3 )
Collection_xyz
Document_xyz
I am not implementing Google Auth, but instead want to send document name Document_sdfljkhsdio as request data and match it with some rules at Firebase Console
service cloud.firestore {
match /databases/{database}/documents {
match /document/{document_sent_from_client}/ {
allow read, write: if request.document_sent_from_client == document_sent_from_client;
}
}
}
Am not sure if it is possible to send document name from Android device ,if yes, please suggest.
And also suggest if it is the correct approach, suggest if you have any better approach ?
Thanks for your help in advance.
Sounds totally feasible. To pass the document_sent_from_client from an Android client, just build a DocumentReference with a path to /document/document_sent_from_client. Modified from the documentation:
DocumentReference docRef = db.collection("document").document("document_sent_from_client");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document != null) {
Log.d(TAG, "DocumentSnapshot data: " + task.getResult().getData());
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
But the security rules don't help here: if the client requests an existing document (i.e. if they know the path to a document), they will get a result. If they request a non-existing document (i.e. if they don't know the path to a document), the task will fail.
You're essentially depending on the key in document_sent_from_client to be reasonably unguessable. Such "security by obscurity" is fairly common in the absence of an authenticated user.
Inside a app, users will upload slot results with period name to the Parse Database. However, before upload, it would be much preferred if beforesave, checked whether the period ref is already there, if the same period ref is existing in the DB, the slot result would not be uploaded.
Cloud.beforesave
Parse.Cloud.beforeSave("check_duplicate", function(request, response)
{
var DB = Parse.Object.extend("Record_db");
var query = new Parse.Query(DB);
query.equalTo("period_ref", request.object.get("period_ref"));
query.first
({
success: function(object)
{
if (object)
{
response.error("A Period with this ref already exists.");
}
else
{
response.success();
}
},
error: function(error)
{
response.error("Could not validate uniqueness for this period ref object.");
}
});
});
Android code:
ParseCloud.callFunctionInBackground("check_duplicate", new HashMap<String, Object>(), new FunctionCallback<String>() {
public void done(String result, ParseException e)
{
if (e == null)
{
Utilities.custom_toast(CurrentResult.this, "cloud success" + result, "gone!", "short");
}
else
{
Utilities.custom_toast(CurrentResult.this, "cloud error" + e, "gone!", "short");
}
}
});
Question:
There is no clear example for such common situation. I would like to ask
for example, now the user would like to upload slot ref 001/2015 results. All info are already available at device, how could I pass this period reference 001/2015 to the cloud code for checking whether it is already existing in the Cloud DB uploading and saving to the Cloud DB?
Thanks a lot!
your first line of Android...
ParseCloud.callFunctionInBackground("check_duplicate", new HashMap(), new FunctionCallback() {
becomes
ParseCloud.callFunctionInBackground("check_duplicate",
new HashMap<String, String>{"period_ref":"001/2015"};,
new FunctionCallback<String>() {