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.
Related
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());
}
}
});
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)
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);
}
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!
Retrieving data from the server may take some seconds. Is there any way to retrieve cached data in the meantime, using a direct get?
The onComplete seems to be called only when the data is retrieved from the server:
db.collection("cities").whereEqualTo("state", "CA").get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
...
}
}
});
Is there any callback for the cached data?
Now it is possible to load data only from cached version. From docs
You can specify the source option in a get() call to change the default behavior.....you can fetch from only the offline cache.
If it fails, then you can again try for the online version.
Example:
DocumentReference docRef = db.collection("cities").document("SF");
// Source can be CACHE, SERVER, or DEFAULT.
Source source = Source.CACHE;
// Get the document, forcing the SDK to use the offline cache
docRef.get(source).addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
// Document found in the offline cache
DocumentSnapshot document = task.getResult();
Log.d(TAG, "Cached document data: " + document.getData());
} else {
Log.d(TAG, "Cached get failed: ", task.getException());
//try again with online version
}
}
});
I just ran a few tests in an Android app to see how this works.
The code you need is the same, no matter if you're getting data from the cache or from the network:
db.collection("translations").document("rPpciqsXjAzjpComjd5j").get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
DocumentSnapshot snapshot = task.getResult();
System.out.println("isFromCache: "+snapshot.getMetadata().isFromCache());
}
});
When I'm online this prints:
isFromCache: false
When I go offline, it prints:
isFromCache: true
There is no way to force retrieval from the cache while you're connected to the server.
If instead I use a listener:
db.collection("translations").document("rPpciqsXjAzjpComjd5j").addSnapshotListener(new DocumentListenOptions().includeMetadataChanges(), new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(DocumentSnapshot snapshot, FirebaseFirestoreException e) {
System.out.println("listen.isFromCache: "+snapshot.getMetadata().isFromCache());
}
}
);
I get two prints when I'm online:
isFromCache: true
isFromCache: false
You can disable network access and run the query to access data from cache .
For firestore :
https://firebase.google.com/docs/firestore/manage-data/enable-offline#disable_and_enable_network_access
For firebase database call db.goOffline() and db.goOnline()