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()
Related
I am using Firebase Firestore (Not the realtime database) and programming an android app with it.
I want my app to work as offline first, which means that when the app loads FireStore will load the data from the Chace and show it to the user, and after that query changes from the server.
To accomplish that, I am doing a normal GET request (Not listener) with source of Chace.
After the get call has completed, I am attaching a listener to listen for all the update. (Locally or from the server), so I Can present the user the updated status.
The problem with this method is that basiclly it means I will download the same object twice First time on the initial get request, and than on the initial listener. Which means I will send duplicates to the UI.
Is there any practice available in the firestore API to avoid this? So the listener will not return records that the get request has already synced?
See below example code. Thx!
GET request: (runs first)
db.collection(sCollection)
.get(SOURCE.CHACE)
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData());
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
Listener: (runs second)
m_registration = db.collection(sName).
orderBy("stamp", Query.Direction.ASCENDING)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen failed.", e);
return;
}
for (DocumentChange dc : value.getDocumentChanges()) {
}
catch(JSONException ex) {
}
}
The Firebase Firestore handles offline so good that you can use it as oofline first database without doing anything on your own. You can read, listen and edit data as if you are doing it synchornously. You should deffinitely check this out.
That means you don't need to use get to make the offline better. It will work seamlesly with the listener to.
My intention here is to display a Toast message if the user inputs an email/password combination that does not match any of the user profiles in the Firestore database.
I've been trying this multiple ways, but it refuses to call the code within "if (!document.exists()))." I've tried omitting "(!document.exists) and just using "else" - nothing.
Everything else works great. "if (document.exists())" happily returns the info and logs in. Please let me know if I need to include more info!
FirestoreRepository:
public void queryUserByEmailPassword(String email, String password) {
Query userQuery = userColRef.whereEqualTo("email", email).whereEqualTo("password", password);
userQuery.get().addOnCompleteListener(querySnapshotOnCompleteListener);
}
LoginViewModel:
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot doc : task.getResult()) {
if (!document.exists()) { // this code will NOT execute
Log.d(TAG, "Error getting documents: ", task.getException());
return;
} else { // this code executes flawlessly
user = doc.toObject(User.class);
currentUser.setValue(user);
doesUserExist.setValue(true);
repo.signIn(user.getEmail(), user.getPassword());
}
}
}
}
It looks like you're assuming that an error with the query will result in a document to show up in a QueryDocumentSnapshot. That's not the way it works. If there's an error with the query, then task.isSuccessful() will return false. You're currently not checking that case.
If you query returns no documents, then your for loop will not execute at all. It is not considered an "error" to get zero documents. If you need to know if there are no documents in the result, you should check that the QuerySnapshot contains no documents:
if (task.isSuccessful()) {
QuerySnapshot qs = task.getResult();
if (qs.size() == 0) {
// the query returned no documents - decide what to do
}
else {
// iterate the documents here
for (QueryDocumentSnapshot snapshot : qs) {
}
}
}
else {
// there was an error, use task.getException() to figure out what happened
}
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 would like to check if a document exists in my collection "users" because when I try this code, it seems not to enter in the OnCompleteListener when the document doesn't exist (when no documents have a correct value with the field "user_uid") :
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("users").whereEqualTo("user_uid", user.getUid())
.limit(1).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
} else {
Log.w(TAG, "Error getting documents.", task.getException());
}
}
});
When I have a document with my condition whereEqualTo("user_uid", user.getUid()), it's working.
So, how can I check if there is a document where the field "user_uid" == user.getUid()?
I copied your code, and tried your implementation of OnCompleteListener with limit(1). It worked with me on both scenarios where there are results, and no results.
Firestore has a way to check if there is no results by checking if it isEmpty() or not, before performing any operations. Because performing any operation will cause IndexOutOfBoundsException most of the time with iterating on getDocuments().
for example:
if (task.isSuccessful()) {
boolean isEmpty = task.getResult().isEmpty();
}
I hope this will help you.
mFirebaseRemoteConfig.fetch(0)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
System.out.println("Fetch Succeeded");
// Once the config is successfully fetched it must be
// values are returned.
mFirebaseRemoteConfig.activateFetched();
} else {
System.out.println("Fetch failed");
}
}
});
I added this to get the remote config from the server. I was able to get the values a couple of times. I updated the remote config conditions after that and now fetch doesnt return anything. Tried a lot of approaches including moving the call after on onResume and calling it from a separate thread. Updating to 9.2.1 also didnt worked for me
What else can be done to get the config?