Firestore fails to retrieve data after reconnecting to the Internet - android

I'm using Firestore with the Android SDK (11.6.2) and I'm hitting an exception when my device was offline and reconnects to the Internet.
When requesting a document, firestore fails with the following task exception :
com.google.firebase.firestore.FirebaseFirestoreException: Failed to get document because the client is offline.
However, the device is connected, I can make network requests beside using Firestore and they succeed.
This error is not consistent, sometimes the request will succeed right after reconnecting to the Internet. Sometimes, the request fails again and again, then succeeds, sometimes more than one minute after the device has been reconnected to the Internet.
Here is a sample request that produces the exception:
val docRef = firestore.collection("foo").document("bar")
docRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("FirestoreSourceSet", "Get document success")
} else {
Log.e("FirestoreSourceSet", "Get document error", task.exception)
}
}
I'm not using the offline capabilities of Firestore, thus the FirebaseFirestore instance is initialized the first time with the setPersistenceEnabled(false) flag:
val firestoreSettings = FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(false)
.build()
val firestore = FirebaseFirestore.getInstance().apply {
this.firestoreSettings = firestoreSettings
}
Why is Firestore returning this error even though the device is online? Am I missing something in the Firestore configuration that would avoid this error?
I tried upgrading Firebase to the 11.8.0 version, but I encounter the same behavior.
Logs
These are the logs while trying to fetch sync some data with Firestore (which begins with a document fetch) after leaving airplane mode: https://pastebin.com/xDMG2Pzj
The network is already available before the first Firestore call, as I waited for the Wifi to settle, and check it using the ConnectivityManager of Android before proceeding with Firestore.
The multiple calls are because I manually retry using a button each time I get the error until the document is successfully retrieved.
The first line of the log is when I turn the airplane mode one, which closes the stream of Firestore.
Edit: Issue
Firebase doesn't have a public tracker, but I reported the issue using their report tool, and made a repo that reproduces the issue.
They acknowledged the issue but could not provide an ETA, so we have to wait:
If we release the fix, we will announce it in our release notes page.
This is still an issue as of firestore-core 17.0.4

From the official documentation:
To use offline persistence, you don't need to make any changes to the code that you use to access Cloud Firestore data. With offline persistence enabled, the Cloud Firestore client library automatically manages online and offline data access and synchronizes local data when the device is back online.
When you initialize Cloud Firestore, you can enable or disable offline persistence. So, when using the following line of code:
val firestoreSettings = FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(false)
.build()
You actually set the persistence to false, you are disabling this feature. To solve this, just remove this line of code. Firestore has .setPersistenceEnabled(true) by default.

This issue was fixed by the release 17.1.5 of Cloud Firestore.
See the official changelog https://firebase.google.com/support/release-notes/android
Cloud Firestore now recovers more quickly from bad network states.
Using my reproduction project with the version 18.0.0, the issue is indeed not present.

I'm facing the same issue since 2018 and have not yet found a proper solution. It is still happening with version 22.1.2 of the Firestore library for Android. As commented in the source code of DocumentReference, this exception is thrown when the client is offline and the requested document is not available in the local cache. There is also a recent explanation of this behaviour by Denver Coneybeare.
This exception might occur for DocumentReference.get() calls. Here's an exemplary stack trace:
com.google.firebase.firestore.FirebaseFirestoreException: Failed to get document because the client is offline.
at com.google.firebase.firestore.DocumentReference.lambda$getViaSnapshotListener$1(DocumentReference.java:331)
at com.google.firebase.firestore.DocumentReference$$Lambda$2.onEvent(DocumentReference.java:8)
at com.google.firebase.firestore.DocumentReference.lambda$addSnapshotListenerInternal$2(DocumentReference.java:504)
at com.google.firebase.firestore.DocumentReference$$Lambda$3.onEvent(DocumentReference.java:16)
at com.google.firebase.firestore.core.AsyncEventListener.lambda$onEvent$0(AsyncEventListener.java:42)
at com.google.firebase.firestore.core.AsyncEventListener$$Lambda$1.run(AsyncEventListener.java:2)
at com.google.firebase.firestore.util.Executors$$Lambda$1.execute(Executors.java)
at com.google.firebase.firestore.core.AsyncEventListener.onEvent(AsyncEventListener.java:39)
at com.google.firebase.firestore.core.QueryListener.raiseInitialEvent(QueryListener.java:176)
at com.google.firebase.firestore.core.QueryListener.onViewSnapshot(QueryListener.java:95)
at com.google.firebase.firestore.core.EventManager.addQueryListener(EventManager.java:97)
at com.google.firebase.firestore.core.FirestoreClient.lambda$listen$6(FirestoreClient.java:160)
at com.google.firebase.firestore.core.FirestoreClient$$Lambda$6.run(FirestoreClient.java:12)
at com.google.firebase.firestore.util.AsyncQueue.lambda$enqueue$2(AsyncQueue.java:436)
at com.google.firebase.firestore.util.AsyncQueue$$Lambda$2.call(AsyncQueue.java:1)
at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor.lambda$executeAndReportResult$1(AsyncQueue.java:322)
at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$$Lambda$2.run(AsyncQueue.java:1)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:229)
at java.lang.Thread.run(Thread.java:919)
I'm sorry if this answer does not solve the problem, but this information would have been too long for a comment on the original post.

I also have the same problem. There is workaround - application waits some time and makes another attempt to get the data from server.
Here is mockup (this is Flutter/dart):
List<Course> items;
Widget build(BuildContext context) {
...
loadData();
...
//building th UI with data from items list
...
}
loadData() async {
//is not loaded but there is internet connection
if (!isLoaded&&isConnected) {
try {
querySnapshot = await query.getDocuments(source: Source.server);
}
catch (ex) {
print(ex);
Future.delayed(Duration(seconds: 10), () {
// setState to trigger build 10 seconds later to make one more attempt
setState(() {
isLoaded = false;
});
});
return;
}
// here goes the code to handle the result
items = querySnapshot.documents.map((s) => Course.fromDb(s)).toList();
}

this may help
Java:
FirebaseFirestore db = FirebaseFirestore.getInstance();
//to reconnect
db.terminate();
db = FirebaseFirestore.getInstance();
Kotlin:
var db = Firebase.firestore
//to reconnect
db.terminate()
db = Firebase.firestore

Related

Amplify.observe not updating based on changes to DynamoDB

I am currently making a call to my server to make a change in the Datastore and listening for another change to it. I do not receive any new changes from the DB despite the database being changed correctly on the backend.
I recently upgraded amplify from 1.4.2 to 1.6.8 and this is when the issue started to show up.
Amplify.DataStore.observe(Profile::class.java,
{ observationCancelable ->
//ERROR
},
{ changedItem ->
when (changedItem.item().state) {
//DO Business logic
}
},
{ exception ->
//ERROR
},
{
}
)
When I query the datastore I am getting outdated results that are not the same as the data on DynamoDB. Is there any way to figure out why the local datastore is not being updated? Could it be an issue with the configuration with the server? Or is there some setup step that I have missed?
Edit: Added some extra details below
So a few more details I wanted to add to this that seem way more relevant as well as updates based on my research. I am switching between endpoints(dev to testing environments). It would seem that the dev environment is working fine with the application, using the datastore correctly and doing the proper AppSync. But in the new testing environment, when the app starts, it gets the latest version of the datastore but it fails to do any AppSync despite being able to make changes to the backend.
Here is an error I am getting when the app attempts to subscribe to the Profile object in the back end
amplify:aws-datastore: Unauthorized failure for ON_CREATE Profile
amplify:aws-datastore: Releasing latch due to an error: Subscription error for Profile: [GraphQLResponse.Error{message='Not Authorized to access onCreateProfile on type Subscription', locations='null', path='null', extensions='{errorType=Unauthorized}'}]
Is there some config file that I should be looking at to compare with to make sure that the endpoints match or some access key that the app needs?

Firebase Firestore how to identify an offline read

I am using Firebase Firestore and I'm facing a problem with the read operation:
I use an onCompleteListener, and inside there, I call different callbacks if the operation was successfull or not.
The problem is, if there is a network issue, the onCompleteListener is called, but task.isSuccessfull returns true!! So I get an empty result which I can't distinguish from a REAL empty result. Is there any way to distinguish a network issue from an empty read?
Thank you very much! My function is just below:
dataBase.collection(COLLECTION)
.whereEqualTo(FIELD, searched)
.get()
.addOnCompleteListener { task: Task<QuerySnapshot> ->
if (task.isSuccessful) {
listenerSuccess(task.result)
} else {
listenerError()
}
}
If you're offline, the client will first try to connect. Once it figures out that it can't connect, it will try to complete the get() based on the data in the local database. That's a valid action on Firestore, so that's why the task is completed successfully.
You can detect if the results came from the local cache vs which came straight from the server, by checking the metadata: querySnapshot.getMetadata().isFromCache(). From the docs:
true if the snapshot was created from cached data rather than guaranteed up-to-date server data. If your listener has opted into metadata updates (via MetadataChanges.INCLUDE) you will receive another snapshot with isFomCache() equal to false once the client has received up-to-date data from the backend.

UNAUTHENTICATED exception when using Firestore from GAE development server

I'm making an Android app that uses Cloud Endpoints Framework for the backend API. I'm testing the backend using the App Engine development server. In one of the backend functions I try to make a call to get a document from a Firestore database but I get this error:
com.google.api.server.spi.SystemService invokeServiceMethod
SEVERE: exception occurred while calling backend method
java.util.concurrent.ExecutionException: io.grpc.StatusRuntimeException: UNAUTHENTICATED
at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:500)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:479)
at com.google.api.core.AbstractApiFuture.get(AbstractApiFuture.java:57)
...
I believe that I have the service account setup properly. I have the environment variable GOOGL_APPLICATION_CREDENTIALS set to the path of the service account key. I'm also following the guide for instantiating and calling Firestore:
import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
FirestoreOptions firestoreOptions =
FirestoreOptions.getDefaultInstance().toBuilder()
.setProjectId(projectId)
.build();
Firestore db = firestoreOptions.getService();
db.collection(collectionId).get().get();
Is there something I'm missing here to be able to call Firestore from GAE?
After fighting with this issue for some time, I incidentally found a temporary workaround. It seems that the db is indeed not authenticated yet and adding a call after getting the service to get the request meta data seems to trigger the authentification...
Firestore db = firestoreOptions.getService();
try {
// somehow without this it does not work on the local server
db.getOptions().getCredentials().getRequestMetadata();
} catch (IOException e) {
e.printStackTrace();
}
... // db access works on local server!

Firestore offline cache

I'm building an Android application which has to work offline for weeks, but can sync immediately with a remote DB as it goes online.
My question is can Firestore be a good option for this? How long does Firestore keep its offline cache?
Firestore can be configured to persist data for such disconnected/offline usage. I recommend that you read the enable offline persistence section in the docs, which contains this sample of enabling this feature:
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(true)
.build();
db.setFirestoreSettings(settings);
This persistence is actually enabled by default on Android and iOS, so the call above is not needed.
Your Android code that interact with the database will be the same whether you're connected or not, since the SDK simply works the same. If you want to detect whether data is coming from the cache (and thus potentially stale), read the section Listen to offline data in the docs.
The data in the cache does not expire after a certain amount of time. The only two reasons data is removed from the cache:
The data has been removed from the server, in which case the client will remove it from the disk cache.
The client needs to purge its disk cache to make space for more recent data.
EDIT-25/5/2020: Francesco is correct, the docs link given in the comment does clarify that. It seems the cache size has been changed, by default it has been decreased to 40MB.
OLD: The following answer follows the official guide in the following link:
Handling Cache size
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
.build();
db.setFirestoreSettings(settings);
The above code has a flag set for setCacheSize(), which will prevent your cache from
being cleared. You can also specify the same in size. If you do not set this by default the size is 100MB.
As per the guide, there is a method to check if the data you query came from cache or the firestore. Also the moment your device is back online the firestore refreshes the cache, and keeps the data synchronized.
To answer your question, as you have to work with offline data for weeks, i suggest every time the data is fetched to store it in json/xml formats, as storing huge amount of data in cache is not a really good approach when thought of in terms of performance.
I hope i helped you clear some things out.
If you listen to data in Cloud Firestore, you will get immediate snapshots of cached data and also updates when your app is able to connect online:
final DocumentReference docRef = db.collection("cities").document("SF");
docRef.addSnapshotListener(new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot snapshot,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen failed.", e);
return;
}
// Determine if the data came from the server or from cache
String source = snapshot != null && snapshot.getMetadata().hasPendingWrites()
? "Local" : "Server";
// Read the data
if (snapshot != null && snapshot.exists()) {
Log.d(TAG, source + " data: " + snapshot.getData());
} else {
Log.d(TAG, source + " data: null");
}
}
});
Persistence is enabled by default so this behavior does not require any configuration.

com.google.firebase.firestore.FirebaseFirestoreException: Failed to get document because the client is offline. Android

I get this run time error that I do not understand the reason behind it.
com.google.firebase.firestore.FirebaseFirestoreException: Failed to get document because the client is offline.
Below is the code in my Activity that tries to fetch data from the cloud Firestore
DocumentReference docRef = db.collection("room-id-1").document("participan-name-1");
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());
userData.registerUserToHotspot(roomId_str, participantName_str);
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
Is there something I can do about this?
This happens because OnCompleteListener is triggered when the task is completed, either it fails or succeeds. To avoid the error, you can use separate OnSuccessListener and OnFailureListener.
OnSuccessListener is called when the task is succeeded, but if the above error occurs, OnFailureListener will be triggered and you can handle the error in onFailure() method of the listener.
Happy Coding :)
If nothing helped and you sure that all good to go, do next steps:
Clean project (Build -> Clean Project). You can also "Invalidate and Caches" with "Clean project" for more safety.
Delete google-services.json from your project and sync.
Build project. Android studio will throw build exception about missing google-services.json, It's okey.
Download again google-services.json from Firebase console, put it in project and sync.
Build and run.
Hoping I'm helped to some one
I had the same exception when I used wrong path to the document. When I fixed the path it started to work.
Anyway, the error message is misleading.
In my case the solution was to create a new emulator in Android Studio and run the app there
if you are using rxjava then it wiil be simple to catch throwable this way:
RxFirestore.getDocument(FirebaseFirestore.getInstance()
.collection("info")
.document("app"))
.subscribeOn(Schedulers.io())
.subscribe(snapshot -> {
}, throwable -> Timber.e(throwable))
Otherwise try somehow catch it in promise or/and try-catch block
For me, I faced this issue because i forgot to initialize my firestore database from the Firebase console. Hope this helps someone else.
I faced this problem recently I tried deleting and re-installing the emulator still no dice. So I decided to catch the error and retry the query if the particular error is present. If the user was really offline I included a time out, if not it will retrieve the data after retries.
fun makeFirestoreQuery(){
fireStoreDb
.collection(Constants.USER_DATA)
.document(currentUserEmail)
.get()
.addOnSuccessListener{documentSnapshot->
//perform opertion with the data
}.addOnFailureListener{exception->
if(exception.message?.contains("Failed to get document because
the client is offline")){
makeFirestoreQuery() //recursively call itself
//if the user is really offline, set a time out
}}
}
I faced with this problem on emulators below API 19 and asked Firebase support team.
They responded to me that is a secure connection problem and they suggested to me enabling TLS on that emulators.
As far as I understand, Firebase requires up-to-date security provider for connections.
If you think all of your Firebase configurations are correct and still facing with this problem on emulators below API 19, be sure emulator has up-to-date security provider.
Please check "TLS on Android" section at this link: https://github.com/grpc/grpc-java/blob/master/SECURITY.md#tls-on-android
Google says it provides a up-to-date provider via Google Play Services. If you want to use that, there is a document.
https://developer.android.com/training/articles/security-gms-provider.html

Categories

Resources