简体   繁体   English

Android 实时数据库抛出错误:客户端离线但未在线

[英]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).该应用程序执行简单的注册(使用 FirebaseAuth、FirebaseUI 和 Google 登录)。 When authenticated successfully , I take firebaseUser.userId and use it to fetch user profile from Realtime Database (example location /users/{userId}/someUserDataIsHere ).身份验证成功后,我获取firebaseUser.userId并使用它从实时数据库中获取用户配置文件(示例位置/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).如果实时数据库为该用户 ID 返回null object,这意味着具有该用户 ID 的用户在实时数据库中不存在,并且是第一次登录(使用 Google 帐户注册),因此应该创建一个配置文件(或换句话说,用户即将注册)。 In other case, if Firebase db returns user object, the app moves forward to the home screen.在其他情况下,如果 Firebase db 返回用户 object,则应用程序会前进到主屏幕。

The user profile contains some mandatory data like userId, email, and a name.用户配置文件包含一些强制性数据,如用户 ID、email 和名称。 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)问题是,有时,当用户启动应用程序并且整个身份验证过程开始时,身份验证成功后,RealtimeDatabase 会尝试获取用户配置文件(对于 FirebaseAuth 提供的 userId),但错误java.lang.Exception: Client is offline发生,返回一个空的 object 所以应用程序“认为”用户是新用户并且必须插入到实时数据库中,并且这样做(即使它说“客户端离线”就像 300 毫秒之前一样)

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.这不会造成大问题,因为它将数据插入到同一个 userId 键(它在技术上执行更新),但请记住我有一些可选字段,当这种情况发生时,这些字段将被重置。 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 2 小时后启动应用程序
  • 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身份验证成功但无法获取该用户 ID 的用户配置文件(有效且存在于实时数据库中),因为客户端处于离线状态错误
  • 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)此外,在应用程序启动时使用它: FirebaseDatabase.getInstance().setPersistenceEnabled(false)

And this is the error I get (UPDATED with logs of some other GET request):这是我得到的错误(更新了一些其他 GET 请求的日志):

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)

Okay, there is a bug in Firebase SDK.好的,Firebase SDK 中有一个错误。

Reported/opened issue on GitHub, and they are about to resolve it.在 GitHub 上报告/打开的问题,他们即将解决它。 Check more on this link此链接上查看更多信息

The main problem was usage of suspending functions with get().await() in the following query:主要问题是在以下查询中使用 get().await() 挂起函数:

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 FlowDataState 只不过是一个可能是 Data 或 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
           }
       }
   }

I was facing the same issue and replaced the get().addOnCompleteListener with addValueEventListener method.我遇到了同样的问题,并用addValueEventListener方法替换了get().addOnCompleteListener Below is completed code.下面是完整的代码。

Code which throws client is offline issue:抛出客户端的代码是离线问题:

databaseReference.child("pages").child("Help").get().addOnCompleteListener { task: Task<DataSnapshot> ->
        if (!task.isSuccessful) {
            Log.e("firebase", "Error getting data", task.exception)
        } else {
            Log.d("firebase",task.result.value.toString())
        }
    }

Code which solved the above issue:解决上述问题的代码:

databaseReference.child("pages").child("Help").addValueEventListener(object : ValueEventListener {
            override fun onDataChange(snapshot: DataSnapshot) {
                Log.e("firebase", "onDataChange ${snapshot.value.toString()}" )
            }

            override fun onCancelled(error: DatabaseError) {
                Log.e("firebase", "onCancelled ${error.message}" )

            }
        })

I had an issue with my rules.我的规则有问题。 The message sometime comes up as "Permission denied" and sometimes as "Client is offline" when there is an issue with rules.当规则出现问题时,该消息有时显示为“权限被拒绝”,有时显示为“客户端离线”。 I fixed my rules and things started working.我修正了我的规则,事情开始起作用了。

Message is misleading at times.消息有时具有误导性。

For me, it ended up being because my google-services.json was out of date after I had updated some things in Firebase.对我来说,这最终是因为我在 Firebase 中更新了一些内容后,我的google-services.json已经过时了。 Particularly it was because my code ended up referencing the default (US-East) region, when I had updated in Firebase to a different one.特别是因为我的代码最终引用了默认(美国东部)区域,当我在 Firebase 中更新到另一个区域时。 Try downloading the latest google-services.json , then making sure you clean and rebuild your app!尝试下载最新的google-services.json ,然后确保cleanrebuild您的应用程序!

What worked for me is re-downloading the google-services.json file, then cleaning and re-building my Android Studio project我有用的是重新下载google-services.json文件,然后清理并重新构建我的 Android Studio 项目

It wasn't working for me because I didn't specify server Url when getting database Instance.它对我不起作用,因为我在获取数据库实例时没有指定服务器 Url。 Besides this error I was getting this in console:除了这个错误,我在控制台中得到了这个:

Firebase Database connection was forcefully killed by the server. Firebase 数据库连接被服务器强行终止。 Will not attempt reconnect.不会尝试重新连接。 Reason: Database lives in a different region.原因:数据库位于不同的区域。 Please change your database URL to请将您的数据库 URL 更改为

To fix it I just specified server like that:为了修复它,我只是指定了这样的服务器:

 FirebaseDatabase.getInstance("YOUR_URL_HERE")

"YOUR_URL_HERE" - you can find this in your firebase console, here: screenshot “YOUR_URL_HERE” - 你可以在你的 firebase 控制台中找到这个,这里:截图

The error is still occurring randomly (no logical explanation as to why), it seems when I build the app (mobile flutter) and use the firebase emulator on the first launch the error would occur.错误仍然随机发生(没有关于原因的逻辑解释),似乎当我构建应用程序(移动颤动)并在第一次启动时使用 firebase 模拟器时会发生错误。
The Solution: I had to manually uninstall the app and then build it again解决方案:我不得不手动卸载该应用程序,然后重新构建它

I've been running into this same issue when using a coroutine with the IO dispatcher:在将协程与 IO 调度程序一起使用时,我遇到了同样的问题:

private fun performFirstReadOfEntities() {
    viewModelScope.launch(ioDispatcher) {
        mLoadingState.value = true
        try {
            Log.d(logTag, "Performing first read of $firebaseDBPath")
            dbEntityRef.get()
                .addOnSuccessListener { snapshot ->
                    val snapMap = snapshot!!.value as Map<*, *>
                    val tempEntityList = mutableListOf<FirebaseEntity>()
                    for (entry in snapMap) {
                        Log.d(logTag, "Adding entry with \n\tkey: ${entry.key}\n\tval: ${entry.value.toString()}")
                        tempEntityList.add(
                            entityFactory.createFirebaseEntity(
                                uid = entry.key as String,
                                map = entry.value as Map<String,Any>))
                    }
                    mEntities.value = tempEntityList.sortedBy { it.uid } // So that list and expected list match for emulation testing
                    Log.d(logTag, "Got ${tempEntityList.size} entities on initial read of database")
                    dbEntityRef.addChildEventListener(childEventListener) // Add child event listener after
                    dbListenersInitialized = true
                    mLoadingState.value = false
                    clearErrorState()
                }
                .addOnFailureListener {
                    setErrorState("Failed to get response from firebase", it)
                }

I'm not sure if this is considered "proper" use, but it was working for me, until I got:我不确定这是否被认为是“正确”使用,但它对我有用,直到我得到:

error: client is offline错误:客户端离线

The solution for me was uninstalling the app launcher from the emulated android device, cleaning/rebuilding, and then re-running the test.我的解决方案是从模拟的 android 设备上卸载应用程序启动器,清理/重建,然后重新运行测试。

Uninstalling the application was what finally did it.卸载应用程序是最终做到的。 Even force closing it didn't do the trick.即使强行关闭它也没有成功。 Maybe there's a firebase issue persisting in the cache and clearing it would have also done the trick?也许缓存中存在 firebase 问题并清除它也可以解决问题? In any case, I'm hoping this is of use to anyone facing the same issue.无论如何,我希望这对面临相同问题的任何人都有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM