【问题标题】:Firestore returns empty snapshot on non-existent data with no internetFirestore 在没有互联网的情况下返回不存在数据的空快照
【发布时间】:2020-11-23 23:51:50
【问题描述】:

Firestore 和 RTDB 在尝试在没有互联网的情况下收听时的行为非常不同。在 Internet 恢复之前,RTDB 不会触发任何侦听器。 Firestore 使用空快照触发侦听器,因此对 exists() 的调用返回 false。当互联网恢复时,Firestore 会再次使用实时数据触发。这是一个没有磁盘持久性的应用程序,并且在设置侦听器之前从未写入正在读取的字段/文档。

firestore 所做的事情会让事情变得非常混乱......可能无法判断数据是否真的不存在或其他地方出了问题。这是预期的行为吗?有什么办法可以解决这个问题吗?

//first firestore statement in the app
fs.collection("blabla").document("blabla").addSnapshotListener(new EventListener<DocumentSnapshot>() {
    @Override
    public void onEvent(@Nullable DocumentSnapshot value, @Nullable FirebaseFirestoreException error)
    {
        if (error == null)
            Log.v("TEST", "FS LISTEN EVENT " + value.exists() + " " + value.getData());
        if (error != null)
            Log.v("TEST", "FS LISTEN ERROR " + error);
    }
});

Logcat:

V/TEST: FS LISTEN EVENT false null

互联网恢复后:

V/TEST: FS LISTEN ERROR com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: 权限缺失或不足。

编辑:

我现在已经测试了这个有没有坚持。至少在第一次获取此数据时具有相同的行为。因为我不打算使用磁盘持久性,所以每次创建活动时都会发生这种情况。

我可以使用 get() 代替快照侦听器 - 它返回一个不错的 com.google.firebase.firestore.FirebaseFirestoreException: 无法获取文档,因为客户端处于脱机状态。非常不同的行为。但我也对 get() 有所保留:

  1. 无法停止/注销它。所以我不能为此操作实现超时。它可以在应用程序暂停时触发 - 或在任何其他意外时刻。 firestore 没有 goOffline ......是的,我可以想出处理它的逻辑 - 虽然肯定很复杂......

  2. 从未得到关于冷火问题是否已解决的答案。有些人记录了 get() 获取数据的 90 秒。对于应用启动时需要的一些关键数据,并且无法实现超时 - 如何处理应用流?

【问题讨论】:

  • “在互联网恢复之前,RTDB 不会触发任何侦听器”这取决于您如何编写代码。没有看到您在 RTDB 中使用的代码,比较两个数据库是没有意义的,我建议您只提出关于 Firestore 的问题。

标签: android firebase firebase-realtime-database google-cloud-firestore


【解决方案1】:

您看到的行为是正常的,并且是意料之中的。

安全规则只在服务器上执行,而不是在客户端缓存上。

这里的逻辑是,对于读取,由于客户端在某个时间点看到了数据,因此它继续可见。对于写入,这是允许快速更新 UI 的问题。如果您愿意,您可以通过在 UI 中反映 hasPendingWrites 来表明写入处于待处理状态。

这里发生的是:

  1. 客户端将写入提交到其本地缓存
  2. 客户端为写入操作触发本地事件,以便应用可以更新其状态。
  3. 客户端将写入发送到服务器
  4. 服务器拒绝写入操作,并向客户端发送否定响应。
  5. 客户端更新其本地状态,并触发协调事件以确保应用也可以更新其状态。

【讨论】:

  • 我同意如果那段数据曾经被写入本地,listen 会返回一些东西。但这是第一次在客户端上执行监听 - 内存、缓存或其他地方没有任何内容。它不存在,也从未存在过。如果没有返回任何内容,我可以实现超时并让用户知道出了点问题。但是如果它返回 null - 我是否认为它在获取这个时有互联网?数据是否存在?那时应用流程变得复杂......
  • 问题是 - 当它有互联网并且文档不存在时,它也会返回一个空快照......如何区分它们?
  • 您可以[调用get() 提供源](firebase.google.com/docs/reference/android/com/google/firebase/…,强制它尝试从服务器检索数据。但用户体验不会很理想。
  • get() 完成了这项工作 - 如果它无法从服务器获取数据,它就会失败。我希望最多 5 秒来获取数据或失败 - 并继续使用应用程序流程。唯一与 RTDB 不同的是,似乎没有办法取消注册 get() 调用。因此,在极端情况下它不会失败 - 但会永远持续并在某个时候返回数据。所以我想我可以在 5 秒后添加忽略数据的逻辑。如果应用程序暂停、侦听器执行、应用程序恢复,我还必须处理这种情况。
  • 使用 RTDB,我通过一个用于所有侦听器的包装类并通过回调添加超时来快速解决这个问题。如果应用程序暂停,所有侦听器都将取消注册。当应用程序恢复时,将再次添加侦听器,并保留适当的超时时间。与此相比似乎很干净......我有点希望 Firebase 最初支持单次获取操作的超时......
【解决方案2】:

可以通过查看快照元数据来检测返回的空快照是否是由于网络问题(Frank 的建议)。如果禁用持久性,则不会对该位置执行任何写入并返回空快照,但 metadata.isFromCache() 返回 true,那么我认为可以假设 Firestore 根本无法从服务器获取数据。相反,如果 metadata.isFromCache() 为 false,则该文档不存在。

fs.collection("users").document(mAuth.getCurrentUser().getUid()).addSnapshotListener(MetadataChanges.INCLUDE, new EventListener<DocumentSnapshot>()
        {
            @Override
            public void onEvent(@Nullable DocumentSnapshot value, @Nullable FirebaseFirestoreException error)
            {
                if (value != null)
                    Log.v("TEST", "FS LISTEN EVENT, exists: " + value.exists() + " data: " + value.getData() + " from cache: " + value.getMetadata().isFromCache());
                if (error != null)
                    Log.v("TEST", "FS LISTEN ERROR " + error);
            }
        });

【讨论】:

    猜你喜欢
    • 2013-10-16
    • 1970-01-01
    • 1970-01-01
    • 2015-05-24
    • 2013-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多