Оффлайн-возможности Firebase и addListenerForSingleValueEvent

Всякий раз, когда я добавлял addListenerForSingleValueEvent с помощью setPersistenceEnabled (true), я мог только получить локальную автономную копию datasnapshot и НЕ обновленный datasnapshot с сервера.

Однако, если я добавил addValueEventListener с помощью setPersistenceEnabled (true), я мог бы получить последнюю копию datasnapshot с сервера.

Является ли это нормальным для addListenerForSingleValueEvent, поскольку он только ищет datasnapshot локально (в автономном режиме) и удаляет свой прослушиватель после успешного извлечения datasnapshot ONCE (либо офлайн, либо онлайн)?

Как работает упорство

Клиент Firebase хранит копию всех данных, которые вы активно слушаете в памяти. Когда последний прослушиватель отключается, данные выводятся из памяти.

Если вы включите жесткость диска в приложении Android Firebase, используя:

Firebase.getDefaultConfig().setPersistenceEnabled(true); 

Клиент Firebase будет хранить локальную копию (на диске) всех данных, которые недавно прослушивали приложение.

Что происходит, когда вы присоединяете слушателя

Скажем, у вас есть следующий ValueEventListener :

 ValueEventListener listener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { System.out.println(snapshot.getValue()); } @Override public void onCancelled(FirebaseError firebaseError) { // No-op } }; 

Когда вы добавляете ValueEventListener в местоположение:

 ref.addValueEventListener(listener); // OR ref.addListenerForSingleValueEvent(listener); 

Если значение местоположения находится в кеше локального диска, клиент Firebase будет onDataChange() вызывать onDataChange() для этого значения из локального кеша. Если затем будет инициировать проверку с сервером, чтобы запросить какие-либо обновления для значения. Он может впоследствии вызвать onDataChange() снова, если произошла смена данных на сервере с момента последнего добавления в кеш.

Что произойдет, если вы используете addListenerForSingleValueEvent

Когда вы добавляете прослушиватель событий одного значения в одно и то же место:

 ref.addListenerForSingleValueEvent(listener); 

Клиент Firebase (как и в предыдущей ситуации) сразу вызывает onDataChange() для значения из локального дискового кэша. Он больше не будет вызывать onDataChange() , даже если значение на сервере оказывается другим. Обратите внимание, что обновленные данные по-прежнему будут запрашиваться и возвращаться при последующих запросах.

Это было рассмотрено ранее в разделе Как работает синхронизация Firebase с общими данными?

Решение и обходное решение

Лучшим решением является использование addValueEventListener() вместо однозначного прослушивателя событий. Регулярный слушатель значений получит как непосредственное локальное событие, так и потенциальное обновление с сервера.

В качестве обходного пути вы также можете вызвать keepSynced(true) в местах, где используется однозначный прослушиватель событий. Это гарантирует, что данные обновляются каждый раз, когда они меняются, что значительно повышает вероятность того, что ваш однозначный прослушиватель событий увидит текущее значение.

Вы можете создать транзакцию и прервать ее, тогда onComplete будет вызываться в режиме онлайн (данные nline) или в автономном режиме (кэшированные данные)

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

 /** * @param databaseReference reference to parent database node * @param callback callback with mutable list which returns list of objects and boolean if data is from cache * @param timeOutInMillis if not set it will wait all the time to get data online. If set - when timeout occurs it will send data from cache if exists */ fun readChildrenOnlineElseLocal(databaseReference: DatabaseReference, callback: ((mutableList: MutableList<@kotlin.UnsafeVariance T>, isDataFromCache: Boolean) -> Unit), timeOutInMillis: Long? = null) { var countDownTimer: CountDownTimer? = null val transactionHandlerAbort = object : Transaction.Handler { //for cache load override fun onComplete(p0: DatabaseError?, p1: Boolean, data: DataSnapshot?) { val listOfObjects = ArrayList<T>() data?.let { data.children.forEach { val child = it.getValue(aClass) child?.let { listOfObjects.add(child) } } } callback.invoke(listOfObjects, true) } override fun doTransaction(p0: MutableData?): Transaction.Result { return Transaction.abort() } } val transactionHandlerSuccess = object : Transaction.Handler { //for online load override fun onComplete(p0: DatabaseError?, p1: Boolean, data: DataSnapshot?) { countDownTimer?.cancel() val listOfObjects = ArrayList<T>() data?.let { data.children.forEach { val child = it.getValue(aClass) child?.let { listOfObjects.add(child) } } } callback.invoke(listOfObjects, false) } override fun doTransaction(p0: MutableData?): Transaction.Result { return Transaction.success(p0) } } 

В коде, если установлен тайм-аут, я устанавливаю таймер, который будет вызывать транзакцию с отменой. Эта транзакция будет вызываться даже в автономном режиме и будет предоставлять онлайн-или кешированные данные (в этой функции действительно существует вероятность того, что эти данные будут кэшированы). Затем я называю транзакцию успешной. OnComplete будет вызван ТОЛЬКО, если мы получим ответ от базы данных firebase. Теперь мы можем отменить таймер (если не null) и отправить данные для обратного вызова.

Эта реализация делает dev на 99% уверенным, что данные находятся в кеше или находятся в режиме онлайн.

Если вы хотите сделать это быстрее в автономном режиме (чтобы не дождаться тупо с тайм-аутом, когда, очевидно, база данных не подключена), проверьте, подключена ли база данных до использования функции выше:

 DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { System.out.println("connected"); } else { System.out.println("not connected"); } } @Override public void onCancelled(DatabaseError error) { System.err.println("Listener was cancelled"); } });