Автономная проблема с Firestore vs Firebase

Я конвертировал одно из своих приложений в новый Firestore. Я делаю что-то вроде сохранения документа нажатием кнопки, а затем в прослушивателе onSuccess, переходя к другому действию.

Я также использую тот факт, что операции сохранения Firestore возвращают задачи, группируя задачи вместе с помощью Tasks.whenAll:

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

      allTasks.addOnSuccessListener([email protected], successListener)
      allTasks.addOnFailureListener([email protected], onFailureListener)

Наконец, я получаю идентификатор документа из успешного сохранения и сохраняю его в настройках или в локальной базе данных для последующего использования (внутри onSuccessListener)

Все это прекрасно работает. До тех пор, пока не произойдет потеря сетевого подключения. Затем все разваливается, потому что задачи никогда не завершаются, и слушатели onSuccess/onFailure/onComplete никогда не получат вызов. Поэтому приложение просто зависает.

Я работаю над этим, проверяя наличие сети перед каждым сохранением, а затем выполняю работу, создавая задачи без каких-либо слушателей. Я также создаю идентификатор документа локально, используя генератор UUID.

Это, BTW, было не тем, как приложение работало со старой firebase. В этом случае все было хорошо в автономном режиме, и я видел, как документы синхронизировались всякий раз, когда приложение приходилось в сети.

Мое обходное решение для Firestore кажется ужасным взломом. Кто-нибудь придумал лучшее решение?

См. связанную базу данных Firestore по обратным вызовам вставки/удаления документа, которые не вызываются при отсутствии подключения addOnCompleteListener не вызывается в автономном режиме с облачным firestore

Ответ 1

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-automatics -sync-он/

Ответ 2

При потере сетевого подключения (нет сетевого подключения на пользовательском устройстве) не срабатывает ни onSuccess(), ни onFailure(). Такое поведение имеет смысл, поскольку задача считается выполненной только тогда, когда данные были переданы (или отклонены) на сервере Firebase. onComplete(Task<T> task) метод вызывается также только тогда, когда задача завершается. Поэтому в случае отсутствия подключения к Интернету не запускается ни onComplete.

Нет необходимости проверять наличие сети перед каждым сохранением. Существует обходное решение, которое легко поможет вам увидеть, действительно ли клиент Firestore не может подключиться к серверу Firebase, который находится под enabling debug logging:

FirebaseFirestore.setLoggingEnabled(true);

Операции, которые записывают данные в базу данных Firestore, определяются как signal completion после того, как они действительно привязаны к бэкэнд. В результате это работает по назначению: в автономном режиме они не будут сигнализировать о завершении.

Обратите внимание, что клиенты Firestore внутренне гарантируют, что вы можете читать свои собственные записи, даже если вы не дожидаетесь завершения удаления задачи.

Ответ 3

Я узнал, как это сделать, используя информацию http://blog.farifam.com. В основном вы должны использовать SnapshotListeners вместо OnSuccess прослушивателей для автономной работы. Кроме того, вы не можете использовать задачи Google, потому что они не будут конкурировать в автономном режиме.

Вместо этого (поскольку задачи в основном Promises), я использовал библиотеку Kotlin Kovenant, которая может подключать слушателей к promises. Один wrinke заключается в том, что вы должны настроить 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
    }
})

Ответ 4

Для поддержки в автономном режиме вам нужно установить 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
        }
    }
});