Firestore与Firebase的离线问题

时间:2017-12-16 06:36:06

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

我将我的一个应用转换为新的Firestore。我正在做一些事情,比如在点击按钮上保存文档,然后在onSuccess监听器中进行其他活动。

我还使用Firestore保存操作返回任务的事实,使用Tasks.whenAll将任务组合在一起:

val allTasks = Tasks.whenAll(
       createSupporter(supporter),,
       setStreetLookup(makeStreetKey(supporter.street_name)),
       updateCircleChartForUser(statusChange, createMode = true), 
       updateStatusCountForUser(statusChange))

      allTasks.addOnSuccessListener(this@SignUpActivity, successListener)
      allTasks.addOnFailureListener(this@SignUpActivity, onFailureListener)

最后,我从成功保存中获取文档ID并将其存储在首选项或本地数据库中供以后使用(在onSuccessListener内)

这一切都很棒。 直到网络连接丢失。然后一切都崩溃了,因为任务永远不会完成,onSuccess / onFailure / onComplete监听器永远不会被调用。所以该应用程序就会挂起。

我正在解决这个问题,方法是在每次保存之前检查网络可用性,然后通过创建没有任何监听器的任务来解决问题。我还使用UUID生成器在本地生成文档ID。

这,BTW,不是应用程序与旧的firebase一起工作的方式。在这种情况下,离线时一切运行良好,我看到文件在应用程序上线时同步。

我对Firestore的解决方法似乎是一个可怕的黑客攻击。有没有人想出更好的解决方案?

查看相关Firestore database on insert/delete document callbacks not being invoked when there is no connection addOnCompleteListener not called offline with cloud firestore

5 个答案:

答案 0 :(得分:12)

Cloud Firestore为我们提供了处理脱机数据的功能,但您需要使用“快照”(QuerySnapshot,DocumentSnapshot)来处理这种情况,遗憾的是它没有很好地记录。这是一些代码示例(我使用Kotlin Android)来处理使用Snapshot的案例:

更新数据:

db.collection("members").document(id)
  .addSnapshotListener(object : EventListener<DocumentSnapshot> {
      override fun onEvent(snapshot: DocumentSnapshot?,
                           e: FirebaseFirestoreException?) {
          if (e != null) {
              Log.w(ContentValues.TAG, "Listen error", e)
              err_msg.text = e.message
              err_msg.visibility = View.VISIBLE;
              return
          }
          snapshot?.reference?.update(data)

      }
  })

添加数据:

db.collection("members").document()
 .addSnapshotListener(object : EventListener<DocumentSnapshot> {
     override fun onEvent(snapshot: DocumentSnapshot?,
                          e: FirebaseFirestoreException?) {
         if (e != null) {
             Log.w(ContentValues.TAG, "Listen error", e)
             err_msg.text = e.message
             err_msg.visibility = View.VISIBLE;
             return
         }
         snapshot?.reference?.set(data)

     }
 })

删除数据:

db.collection("members").document(list_member[position].id)
   .addSnapshotListener(object : EventListener<DocumentSnapshot> {
       override fun onEvent(snapshot: DocumentSnapshot?,
                            e: FirebaseFirestoreException?) {
           if (e != null) {
               Log.w(ContentValues.TAG, "Listen error", e)
               return
           }
           snapshot?.reference?.delete()
       }
   })

您可以在此处查看代码示例:https://github.com/sabithuraira/KotlinFirestore和博文http://blog.farifam.com/2017/11/28/android-kotlin-management-offline-firestore-data-automatically-sync-it/

答案 1 :(得分:2)

当网络连接丢失时(用户设备上没有网络连接),onSuccess()onFailure()都不会被触发。这种行为是有道理的,因为只有在Firebase服务器上提交(或拒绝)数据时才会认为该任务已完成。仅在任务完成时才调用onComplete(Task<T> task)方法。因此,如果没有互联网连接,则onComplete都不会被触发。

每次保存前无需检查网络可用性。有一种解决方法可以帮助您轻松查看Firestore客户端是否确实无法连接到Firebase服务器,这是enabling debug logging

FirebaseFirestore.setLoggingEnabled(true);

将数据写入Firestore数据库的操作在实际提交到后端后定义为signal completion。因此,这可以按预期工作:离线时,它们不会发出完成信号。

请注意,即使您没有等待删除任务完成,Firestore客户端也会在内部保证您可以读取自己的写入。

答案 2 :(得分:1)

我在http://blog.farifam.com使用信息找到了如何做到这一点。 基本上,您必须使用SnapshotListeners而非OnSuccess侦听器进行离线工作。 此外,您无法使用Google的任务,因为他们不会离线竞争。

相反(因为任务基本上是Promises),我使用了Kotlin Kovenant 库,它可以将听众附加到promises上。一个问题是你必须配置Kovenant以允许多个解析承诺,因为事件监听器可以被调用两次(一旦数据被添加到本地缓存,一次被同步到服务器)。

以下是一个代码片段,包含成功/失败侦听器,可在线和离线运行。

val deferred = deferred<DocumentSnapshot, Exception>() // create a deferred, which holds a promise
// add listeners
deferred.promise.success { Log.v(TAG, "Success! docid=" + it.id) }
deferred.promise.fail { Log.v(TAG, "Sorry, no workie.") }

val executor: Executor = Executors.newSingleThreadExecutor()
val docRef = FF.getInstance().collection("mydata").document("12345")
val data = mapOf("mykey" to "some string")

docRef.addSnapshotListener(executor, EventListener<DocumentSnapshot> { snap: DocumentSnapshot?, e: FirebaseFirestoreException? ->
    val result = if (e == null) Result.of(snap) else Result.error(e)
    result.failure {
        deferred.reject(it) // reject promise, will fire listener
    }
    result.success { snapshot ->
        snapshot.reference.set(data)
        deferred.resolve(snapshot) // resolve promise, will fire listener
    }
})

答案 3 :(得分:0)

要获得离线支持,您需要设置Source.CACHE

docRef.get(Source.CACHE).addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        if (task.isSuccessful()) {
            // Document found in the offline cache
            DocumentSnapshot document = task.getResult();

        } else {
            //error
        }
    }
});

答案 4 :(得分:0)

此代码使用 Dart 语言,因为我在 Flutter 中使用,但您可以轻松将其更改为您的平台和语言

Future<void> updateDoc(String docPath, Map<String, dynamic> doc) async {
    doc["updatedAt"] = Utils().getCurrentTimestamp();
    DocumentReference documentReference = _firestore.doc(docPath);

    Completer completer = Completer();
    StreamSubscription streamSubscription;
    streamSubscription = documentReference
        .snapshots(includeMetadataChanges: true)
        .listen((DocumentSnapshot updatedDoc) {
      // Since includeMetadataChanges is true this will stream new data as soon as
      // it update in local cache so it data has same updateAt it means it new data
      if (updatedDoc.data()["updatedAt"] == doc["updatedAt"]) {
        completer.complete();
        streamSubscription.cancel();
      }
    });
    documentReference.update(doc);
    return completer.future;
  }

因此,由于设置了 includeMetadataChanges,它会在本地缓存更改为时将数据发送到流,因此当您调用 update 时,您将在本地缓存更新后立即收到数据。你可以用Completer来完成你的未来。现在您的方法只等待更新本地缓存,您可以将 await 用于 updateDoc