Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
419 views
in Technique[技术] by (71.8m points)

firebase - Android Realtime database throws error: client is offline but it's not

The App performs a simple sign up (using FirebaseAuth, FirebaseUI & Google Sign In). When authenticated successfully, I take firebaseUser.userId and use it to fetch user profile from Realtime Database (example location /users/{userId}/someUserDataIsHere).

In case Realtime Database returns null object for that userId, it means the user with that userId does not exist in realtime DB, and is signing in for the first time (using Sign up with Google Account), hence a profile should be created (or in other words, user is about to register). In other case, if Firebase db returns user object, the app moves forward to the home screen.

The user profile contains some mandatory data like userId, email, and a name. But also contains some optional data like age, country etc, that could be empty.

The problem is that from time to time, when a user starts the app and the whole authentication process starts, after successful authentication, RealtimeDatabase tries to fetch user profile (for userId provided by FirebaseAuth), but error java.lang.Exception: Client is offline occurs, returns an empty object so the app "thinks" the user is new and must be inserted in the Realtime Database, and does that (even if it said "Client is offline" like 300ms before)

How it is offline when it authenticated user a few milliseconds before, failed to fetch data for that user from the realtime database (because it is offline??), and managed to write a new profile to the realtime database few ms after?

It does not make a huge problem, because it inserts data to the same userId key (it performs update technically), but remember that I have some optional fields, and those will be reset when this case happens. It is strange from the user's perspective because the user entered some optional fields (for example age) and it disappeared after some time.

I must point out the most usual use case for this:

  • User starts the app, sings in successfully and it is authenticated, provided with all the data for operating the app on a Home screen
  • Exit/kills the app
  • Starts the app after 2 hours
  • Authenticates successfully but fails to fetch user profile for that userId (which is valid and exists in the Realtime Database) due to Client is Offline error
  • Performs new user insertion successfully

Some of the dependencies that I use in the app ->

    implementation platform('com.google.firebase:firebase-bom:26.4.0')
    implementation 'com.google.firebase:firebase-analytics-ktx'
    implementation 'com.google.firebase:firebase-auth-ktx'
    implementation 'com.google.firebase:firebase-messaging-ktx'
    implementation 'com.google.firebase:firebase-database-ktx'
    implementation 'com.firebaseui:firebase-ui-auth:6.2.0'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'
    implementation 'com.google.android.gms:play-services-auth:19.0.0'

Also, using this on app start: FirebaseDatabase.getInstance().setPersistenceEnabled(false)

And this is the error I get (UPDATED with logs of some other GET request):

2021-01-30 16:12:12.210 9157-9599/com.fourexample.oab D/PersistentConnection: pc_0 - Connection interrupted for: connection_idle
2021-01-30 16:12:12.221 9157-9599/com.fourexample.oab D/Connection: conn_0 - closing realtime connection
2021-01-30 16:12:12.221 9157-9599/com.fourexample.oab D/WebSocket: ws_0 - websocket is being closed
2021-01-30 16:12:12.224 9157-9599/com.fourexample.oab D/PersistentConnection: pc_0 - Got on disconnect due to OTHER
2021-01-30 16:12:12.372 9157-9599/com.fourexample.oab D/WebSocket: ws_0 - closed
2021-01-30 16:13:07.094 9157-9166/com.fourexample.oab I/zygote64: Debugger is no longer active
2021-01-30 16:13:08.682 9157-9599/com.fourexample.oab D/Persistence: Starting transaction.
2021-01-30 16:13:08.687 9157-9599/com.fourexample.oab D/Persistence: Saved new tracked query in 3ms
2021-01-30 16:13:08.705 9157-9599/com.fourexample.oab D/Persistence: Transaction completed. Elapsed: 22ms
2021-01-30 16:13:11.708 9157-9599/com.fourexample.oab D/PersistentConnection: pc_0 - get 1 timed out waiting for connection
2021-01-30 16:13:11.713 9157-9157/com.fourexample.oab I/RepoOperation: get for query /requests/rs falling back to cache after error: Client is offline
2021-01-30 16:13:11.715 9157-9157/com.fourexample.oab D/Persistence: Starting transaction.
2021-01-30 16:13:11.718 9157-9157/com.fourexample.oab D/Persistence: Saved new tracked query in 2ms
2021-01-30 16:13:11.726 9157-9157/com.fourexample.oab D/Persistence: Transaction completed. Elapsed: 9ms
2021-01-30 16:13:11.741 9157-9157/com.fourexample.oab E/RequestService: java.lang.Exception: Client is offline
        at com.google.firebase.database.connection.PersistentConnectionImpl$2.run(PersistentConnectionImpl.java:432)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
        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:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)

question from:https://stackoverflow.com/questions/65927977/android-realtime-database-throws-error-client-is-offline-but-its-not

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Okay, there is a bug in Firebase SDK.

Reported/opened issue on GitHub, and they are about to resolve it. Check more on this link

The main problem was usage of suspending functions with get().await() in the following query:

val dataSnapshot = firebaseRoutes.getRequestsReference(countryCode)
            .orderByChild("isActive").equalTo(true)
            .limitToFirst(20)
            .get()
            .await()

This would randomly close connection with Realtime Database. I came up with a workaround using extensions until they solve it on their end.

So if you want to use queries and suspending functions, check this extension:

suspend inline fun <reified T> Query.awaitSingleValueEventList(): Flow<FlowDataState<List<T>>> =
callbackFlow {
    val valueEventListener = object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {
            try {
                val entityList = mutableListOf<T>()
                snapshot.children.forEach { dataSnapshot ->
                    dataSnapshot.getValue<T>()?.let {
                        entityList.add(it)
                    }
                }
                offer(FlowDataState.Success(entityList))
            } catch (e: DatabaseException) {
                offer(FlowDataState.Error(e))
            }
        }

        override fun onCancelled(error: DatabaseError) {
            offer(FlowDataState.Error(error.toException()))
        }
    }

    addListenerForSingleValueEvent(valueEventListener)

    awaitClose { removeEventListener(valueEventListener) }
}

Usage:

suspend fun getActiveRequests(countryCode: String): Flow<FlowDataState<List<RequestEntity>>> {
    return firebaseRoutes.getRequestsReference(countryCode)
        .orderByChild("isActive").equalTo(true)
        .limitToFirst(20)
        .awaitSingleValueEventList()
}

FlowDataState is nothing but a wrapper that could be Data or Error

sealed class FlowDataState<out R> {
    data class Success<out T>(val data: T) : FlowDataState<T>()
    data class Error(val throwable: Throwable) : FlowDataState<Nothing>()
}

Calling this:

service.getActiveRequests(countryCode).collect {
       when (it) {
           is FlowDataState.Success -> {
               // map from entity list to domain model list
               // and emit to ViewModel
           }
           is FlowDataState.Error -> {
               // emit error to viewModel
           }
       }
   }

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...