setting security rules in Cloud Firestore - android

The bottom line is that I am writing an application so that when you click on a button in an application, a person receives a promotional code from an array in the database and deletes this received line, but a problem has arisen, while simultaneously (or within 1-2 seconds) on different devices received one and also a value, since it simply does not have time to be removed from the base, so is it possible in the security rules to ask the users to access the database one by one or somehow solve this problem? Thank you in advance
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
Button mButtonGive;
Button mButtonGiveIvi;
TextView mTextPromo;
TextView mTextPromoIvi;
FirebaseFirestore mRef = FirebaseFirestore.getInstance();
DocumentReference mDelRef = mRef.collection("Promocode").document("Delivery");
DocumentReference mIviRef = mRef.collection("Promocode").document("Ivi");
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButtonGive = findViewById(R.id.mButtonGive);
mButtonGiveIvi = findViewById(R.id.mButtonGiveIvi);
mTextPromo = findViewById(R.id.mTextPromo);
mTextPromoIvi = findViewById(R.id.mTextPromoIvi);
mButtonGive.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mDelRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
assert document != null;
if (document.exists()) {
Log.d(TAG, "DocumentSnapshot data: " + document.getData());
List<String> promocodes;
promocodes = (List<String>) document.get("Promocode");
Log.d(TAG, "Promocodes: " + promocodes);
if (promocodes.size() > 0) {
mTextPromo.setText(promocodes.get(0));
mDelRef.update("Promocode", FieldValue.arrayRemove(promocodes.get(0)));
} else {
mTextPromo.setText("Промокоды кончились");
Log.d(TAG, "No Promo");
}
} else {
Log.d(TAG, "No such document");
}
}
else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
}
});

Since you posted no code or any snippet of your program:
I'm guessing you are sending a code to a user, and he has to introduce it in the app. Make another array where you keep track of sent codes that hasn't been used in the app yet: promotionalCodes[ ] and sentCodes[ ] for example. Once you want to send another code you should check if the code is in sentCodes[] and promotionalCodes[] so you don't send duplicates.
(It'd be easier if you attach some of the code or phrase your question better)

Related

why are there so many reads on my firestore?

I have the following code for an activity of my app. It works by redirecting from another activity. But even though it's run once, it does hundreds of readings. Where is the problem?
public class CalculatedResults extends AppCompatActivity {
private Intent intent;
private float clearTurkıshCorrect, clearSocialStudiesCorrect, clearBasicMathCorrect, clearScienceCorrect;
private TextView txtClearTurkishCorrect,txtClearSocialStudiesCorrect, txtClearBasicMathCorrect,txtClearScienceCorrect, txtRawScore, txtPlacementScore, txtRawRanking, txtPlacementRanking;
FirebaseFirestore db = FirebaseFirestore.getInstance();
private static final String TAG = "DocSnippets";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calculated_results);
setDataFromFirebase();
}
private void setDataFromFirebase() {
DocumentReference docRef = db.collection("rawRankings").document(String.valueOf((int)intent.getDoubleExtra("rawScore",0)));
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());
txtRawRanking.setText(String.valueOf(document.getData().get("tyt")));
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
}
}
Posting the comments of #VenkateshTalacheeru, #FrankvanPuffelen as a Community wiki answer.
Firebase Firestore read counts are calculated even you load data in your Firebase console. It will read all the collections and documents you have every time you refresh or real-time syncs. It's expected behavior.
As was mentioned in another thread,
use of the Firebase console will incur reads. If you leave the console
open on a collection/document with busy write activity, the console
will automatically read the changes that update the console's display.
This is very often the source of unexpected reads.

Anonymous Firebase sign in method not working

really hoping someone can help me out here as I've been stuck for a few weeks with this and have no idea if I'm headed down the right path. My ultimate goal is to download images that I manually uploaded to my firebase storage and then retrieve and store the download urls in an array for passing to my fragment's recyclerview adapter.
I setup the necessary firebase implementations, configured the console, linked the android/firebase projects etc, and then drafted some code for downloading the images from storage. After many incorrect attempts of my own I went with some pretty standard code that I saw on firebase and SO articles.
But upon execution, the array would not fill and I would get a null pointer exception at the "mImageID.get(position)" in my onBindViewHolder() method shown below.
public void onBindViewHolder(#NonNull ViewHolder viewHolder, final int position) {
Log.d(TAG, "onBindViewHolder: was called");
if(mImageID.get(position) != null){
Glide.with(viewHolder.imageItem.getContext())
.load(mImageID.get(position))
.error(R.drawable.ic_launcher_background)
.fallback(R.drawable.ic_launcher_background)
.into(viewHolder.imageItem);
}else if(mImageID.get(position) == null){
Log.d(TAG, "onBindViewHolder: image URLs not filled to array");
}
I then noticed that the stack trace had a "no user authentication" warning prior to this exception even though my application doesn't have a sign in activity. I thought that might be the issue so I:
Enabled anonymous sign in on console
Setup FirebaseAuth and FirebaseUser variable
Created a method to signInAnonymously
The signInAnonymously method then triggers the download image Url method
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.d(TAG, "onCreateView: started");
View root = inflater.inflate(R.layout.fragment_list, container, false);
RecyclerView recyclerView = root.findViewById(R.id.listView_view);
//add array for each item
mAuthor = new ArrayList<>();
mTitle = new ArrayList<>();
//authentication for Firebase access
mAuth = FirebaseAuth.getInstance();
mUser = mAuth.getCurrentUser();
if(mUser != null){
Log.d(TAG, "onCreateView: User object not null");
downloadFirebaseImages();
}else{
Log.d(TAG, "onCreateView: User object null, running anonymousAuth()");
anonymousAuth();
}
This removed the null pointer exception. However it doesn't seem to initate an anonymous sign in and subsequent imageURL download.
Basically it gets to the anonymousAuth() method but it doesn't run the signInAnonymously() method. What's weird is that even when attaching an onFailureListener, it doesn't print out the log.debug message within. So it's not like the task is failing. It's like it doesn't even run at all.
The logcat finishes here:
2021-02-13 19:24:22. D/LIST: onCreateView: started
2021-02-13 19:24:22. D/LIST: onCreateView: User object null, running anonymousAuth()
Thanks in advance for any assistance you could provide.
private void downloadFirebaseImages(){
mImageURL = new ArrayList<>();
mImagesRef = FirebaseStorage.getInstance().getReference();
for (int i = 1; i <= 14; i++) {
Log.d(TAG, "downloadFirebaseImage: attempted to fill image array");
String imageRef = Integer.toString(i);
mImagesRef.child(imageRef).getDownloadUrl()
.addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
mImageURL.add(uri);
Log.d(TAG, "getDownloadUrl successful: added uri to arraylist");
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "getDownloadUrl failed: unable to retrieve firebase images");
}
});
}
halter = false;
}
private void anonymousAuth() {
mAuth.signInAnonymously().addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
Log.d(TAG, "signInAnonymously() succeeded: signed in anonymously");
downloadFirebaseImages();
}
});
When onComplete is called it means that sign in completed,not necessarily that is succeeded.
You should check if the task succeeded, or use an addOnSuccessListener. The latter would look something like:
mAuth.signInAnonymously().addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessfull()) {
Log.d(TAG, "signInAnonymously() succeeded: signed in anonymously");
downloadFirebaseImages();
}
else {
Log.e(TAG, "signInAnonymously() failed", task.getException());
}
}
});

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);
}

firestore read is asynchronous and I want synchronous behaviour

I want to store locally the data I am reading from the cloud.
To achieve this I am using a global variable(quizzes) to hold all the data.
For this, when I am building my Quiz objects, I need to make sure that before I am creating them, the relevant data has been already downloaded from the cloud. Since when reading data from firestore, it happens asynchronously.
I didn't enforced this (waiting for the read to finish) before -I just used onSuccess listeners, and I encountered synchronization problem because the reading tasks weren't finished before I created my Quiz objects with the data from the cloud.
I fixed this with a very primitive way of "busy waiting" until the read from the cloud is complete. I know this is very stupid, a very bad practice, and making the application to be super slow, and I am sure there is a better way to fix this.
private void downloadQuizzesFromCloud(){
String user_id = FirebaseAuth.getInstance().getCurrentUser().getUid();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
CollectionReference quizzesRefrence = db.collection("users").document(user_id).collection("quizzes");
Task<QuerySnapshot> task = quizzesRefrence.get();
while(task.isComplete() == false){
System.out.println("busy wait");
}
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
}
I looked online in the documentation of firestore and firebase and didn't find anything that I could use. (tried for example to use the "wait" method) but that didn't help.
What else can I do to solve this synchronization problem?
I didn't understand if you tried this solution, but I think this is the better and the easier: add an onCompleteListener to the Task object returned from the get() method, the if the task is succesfull, you can do all your stuff, like this:
private void downloadQuizzesFromCloud(){
String user_id = FirebaseAuth.getInstance().getCurrentUser().getUid();
final FirebaseFirestore db = FirebaseFirestore.getInstance();
CollectionReference quizzesRefrence = db.collection("users").document(user_id).collection("quizzes");
quizzesRefrence.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccesful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
}
});
}
}
In this way, you'll do all you have to do (here the for loop) as soon as the data is downloaded
You can make your own callback. For this, make an interface
public interface FireStoreResults {
public void onResultGet();
}
now send this call back when you get results
public void readData(final FireStoreResults){
db.collection("users").document(user_id).collection("quizzes")
.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (QueryDocumentSnapshot document : task.getResult()) {
Quiz quizDownloaded = getQuizFromCloud(document.getId());
quizzes.add(quizDownloaded);
}
results.onResultGet();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
results.onResultGet();
}
});
}
Now in your activity or fragment
new YourResultGetClass().readData(new FireStoreResults(){
#Override
public void onResultGet() {
new YourResultGetClass().getQuizzes(); //this is your list of quizzes
//do whatever you want with it
}
Hope this makes sense!

Order of ArrayList changes when updating from Firestore Database

Problem: My list items display in the wrong order. This happens when I close the fragment and re-open it. It then displays all the "sent messages" first, and then the received messages after. However, when I'm in writing the messages, they appear in the correct order. It's only when I close the fragment/activity and re-open it that the order has changed.
I call the getMessages method in my on-create method for opening the fragment containing the view.
What I've tried:
Using the Firestore orderby method (both with String and TimeStamp)
Using the simpler Firestore Snapshot Listener
Question:
How do I best use the Firestore Snapshot Listener with a RecyclerView and maintain the order of the items correctly?
Here is my main "getMessages" method:
public void getLiveChatMessages(final ArrayList<ChatConversationMessage> messageArrayList, final ChatConversationAdapter adapter, final String matchClicked) {
final String userID = onboardingFirebaseUser.returnCurrentUserId();
final CollectionReference messagesCollectionRef = db.collection("users")
.document(userID)
.collection("matches")
.document(matchClicked)
.collection("messages");
messagesCollectionRef
.orderBy("TimeStamp", Query.Direction.DESCENDING)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "listen:error", e);
return;
}
for (QueryDocumentSnapshot doc : value) {
if (doc.get("Message") != null) {
if (doc.get("Message") != null && doc.get("From user with ID").equals(userID)) {
String message = doc.getString("Message");
messageArrayList.add(new ChatConversationMessage(message));
adapter.notifyDataSetChanged(); //Ensures messages are visible immediately
} else if (doc.get("Message") != null) {
final String message = doc.getString("Message");
DocumentReference matchRef = db.collection("users")
.document(userID)
.collection("matches")
.document(matchClicked);
matchRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
imageReference = storageReference.child(document.getString("profileImg"));
messageArrayList.add(new ChatConversationMessage(message, imageReference));
adapter.notifyDataSetChanged(); //Ensures messages are visible immediately
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
}
}
}
}
});}}
After some time I've found the problem.
I was calling a the get-method on a new reference within the snapshot listener. When you do this, it impacts the order of the items in your ArrayList.
To solve it, ensure that all the items you need from Firestore to create your ArrayList is stored in the same location as fields on each document (and not in two different locations). That way, you don't need to use a separate get-method on a new reference within a snapshot listener. This also keeps client-side code cleaner. For future Googlers, here is how I restructured my method:
messagesCollectionRef
.orderBy("TimeStamp", Query.Direction.ASCENDING)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot snapshots,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "listen:error", e);
return;
}
for (DocumentChange dc : snapshots.getDocumentChanges()) {
switch (dc.getType()) {
case ADDED:
Log.d(TAG, "New message added" + dc.getDocument().getData());
if (dc.getDocument().get("Message") != null && dc.getDocument().get("From user with ID").equals(userID)) {
String message = dc.getDocument().getString("Message");
messageArrayList.add(new ChatConversationMessage(CURRENTUSER, message, null));
adapter.notifyDataSetChanged();
}
if (dc.getDocument().get("Message") != null && dc.getDocument().get("From user with ID").equals(matchClicked)) {
String message = dc.getDocument().getString("Message");
imageReference = storageReference.child(dc.getDocument().getString("profileImg"));
messageArrayList.add(new ChatConversationMessage(OTHERUSER, message, imageReference));
adapter.notifyDataSetChanged();
}
break;
case MODIFIED:
break;
case REMOVED:
break;
}
}
}
});
As you can see, I've now stored the imageReference String within each message doc in Firestore, and it can be retrieved in the same way I retrieve all the other data I need to make an addition to my ArrayList. The major code change you need to do is where you write your data to the cloud (ie. write/set Firestore docs). That's where you'll need to make sure that everything is added as field values to your doc, so you don't need to get it in separate locations. Good luck!

Categories

Resources