0

I'm trying to request data from firestore in a background thread with setting the source to Source.Server, with the help of Play services Task API as the following:

suspend fun getUsers() = withContext(IO) { try { val ref = Firebase.firestore.collection("users") val task = ref.get(Source.SERVER) val snapshot = Tasks.await(task) val users = snapshot.map { it.toObject(User::class.java) } } catch (e: Exception) { if (e is FirebaseFirestoreException) { Log.i(TAG, "FirebaseFirestoreException: ${e.localizedMessage}") } else if (e is ExecutionException){ //the mentioned error satisfy this if clause, not the first one Log.i(TAG, "ExecutionException: ${e.localizedMessage}") }else{ Log.i(TAG, "Not a FirebaseFirestoreException nor an Excecution Exception: ${e.localizedMessage}") } } } 

Sometimes the above code gives me this error:

2020-09-29 18:03:51.814 19502-19502/com.first.academy I/Fuck: ExecutionException: com.google.firebase.firestore.FirebaseFirestoreException: Failed to get documents from server. (However, these documents may exist in the local cache. Run again without setting source to SERVER to retrieve the cached documents.)

And app crashes with this error when I run the code whithout try catch block:

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.first.academy, PID: 24934 java.util.concurrent.ExecutionException: com.google.firebase.firestore.FirebaseFirestoreException: Failed to get documents from server. (However, these documents may exist in the local cache. Run again without setting source to SERVER to retrieve the cached documents.) at com.google.android.gms.tasks.Tasks.zzb(Unknown Source) at com.google.android.gms.tasks.Tasks.await(Unknown Source) at com.first.academy.main.MainViewModel$getUsers$2.invokeSuspend(MainViewModel.kt:88) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) Caused by: com.google.firebase.firestore.FirebaseFirestoreException: Failed to get documents from server. (However, these documents may exist in the local cache. Run again without setting source to SERVER to retrieve the cached documents.) at com.google.firebase.firestore.Query.lambda$getViaSnapshotListener$1(Query.java:995) at com.google.firebase.firestore.Query$$Lambda$2.onEvent(Query.java) at com.google.firebase.firestore.Query.lambda$addSnapshotListenerInternal$2(Query.java:1142) at com.google.firebase.firestore.Query$$Lambda$3.onEvent(Query.java) 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) 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.onOnlineStateChanged(QueryListener.java:116) at com.google.firebase.firestore.core.EventManager.handleOnlineStateChange(EventManager.java:178) at com.google.firebase.firestore.core.SyncEngine.handleOnlineStateChange(SyncEngine.java:361) at com.google.firebase.firestore.core.MemoryComponentProvider$RemoteStoreCallback.handleOnlineStateChange(MemoryComponentProvider.java:114) at com.google.firebase.firestore.remote.RemoteStore$$Lambda$1.handleOnlineStateChange(RemoteStore.java) at com.google.firebase.firestore.remote.OnlineStateTracker.setAndBroadcastState(OnlineStateTracker.java:176) at com.google.firebase.firestore.remote.OnlineStateTracker.handleWatchStreamFailure(OnlineStateTracker.java:149) at com.google.firebase.firestore.remote.RemoteStore.handleWatchStreamClose(RemoteStore.java:481) at com.google.firebase.firestore.remote.RemoteStore.access$200(RemoteStore.java:53) at com.google.firebase.firestore.remote.RemoteStore$1.onClose(RemoteStore.java:181) at com.google.firebase.firestore.remote.AbstractStream.close(AbstractStream.java:344) at com.google.firebase.firestore.remote.AbstractStream.handleServerClose(AbstractStream.java:398) at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.lambda$onClose$3(AbstractStream.java:151) at com.google.firebase.firestore.remote.AbstractStream$StreamObserver$$Lambda$4.run(AbstractStream.java) at com.google.firebase.firestore.remote.AbstractStream$CloseGuardedRunner.run(AbstractStream.java:67) at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.onClose(AbstractStream.java:137) at com.google.firebase.firestore.remote.FirestoreChannel$1.onClose(FirestoreChannel.java:135) at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:426) at io.grpc.internal.ClientCallImpl.access$500(ClientCallImpl.java:66) E/AndroidRuntime: at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:689) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$900(ClientCallImpl.java:577) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:751) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:740) at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37) at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:229) at java.lang.Thread.run(Thread.java:776)

The exception thrown when this error happens is not a FirebaseFirestoreException, and doesn't satisfy the first if clause, even though the error is related to firestore as the message says, rather it's an ExecutionException.

How to handle this particular error to show different message to the user? How to know if the reason is this specific error when an exception is thrown because the ExcecutionException could be thrown for different reasons?

6
  • I don't understand what you're saying about the exception. Please edit the question to be more specific about what you're observing. Commented Sep 29, 2020 at 14:46
  • @DougStevenson Edited. Sorry for the confusion! Commented Sep 29, 2020 at 15:11
  • I'm asking for more details about this unexpected exception. Do you have a full stack trace with details like a specific error message? Please be specific about what you're observing. Commented Sep 29, 2020 at 15:16
  • @DougStevenson There's no stack trace, the exception is handled in the second if clause and a log message gets printed with the provided error message. It happens when user is offline or connection is slow. Commented Sep 29, 2020 at 15:45
  • The error message doesn't look like it's coming from the block of code checked by e is ExecutionException. Nowhere in that message does it say "ExcecutionException". Commented Sep 29, 2020 at 15:52

1 Answer 1

1

The problem is that your code is catching an error that doesn't come directly from the invocation of a Firestore API. You're catching an error that comes from Tasks.await(), which is not a Firestore API. The Firestore error is embedded as the cause of the ExecutionException, and you can find it by looking at the cause property (getCause() method) of the ExecutionException that you caught.

To be honest, you should consider abandoning using the Task API like this. Tasks.await() isn't really meant to be used as you are doing now. Instead, it's better to use the kotlinx-coroutines-play-services library to add an extension function that lets you more easily work with Task objects in a coroutine, as I describe in this answer: How To Use Async/Await/Coroutines In OnCompleteListener Firebase

Sign up to request clarification or add additional context in comments.

5 Comments

If it isn't meant to be used like that why would google give a similar example on the official documentation on the blocking section? here: developers.google.com/android/guides/tasks
It's not for use in Kotlin coroutines. That example is when you absolutely need to block a thread, which is an antipattern in coroutine programming. Coroutines are not supposed to block - they are supposed to suspend.
Got it. Another question, there's some warning on the await() extension function that a declaration is experimental, what does that mean?
If you have another question, I suggest posting it separately.
Can you answer this related question please ?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.