Автономная проблема с 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
}
}
});