Оффлайн-возможности Firebase и addListenerForSingleValueEvent
Всякий раз, когда я использую addListenerForSingleValueEvent
с setPersistenceEnabled(true)
, мне удается получить локальную автономную копию DataSnapshot
и NOT обновленного DataSnapshot
с сервера.
Однако, если я использую addValueEventListener
с setPersistenceEnabled(true)
, я могу получить последнюю копию DataSnapshot
с сервера.
Это нормально для addListenerForSingleValueEvent
, поскольку он только ищет DataSnapshot
локально (в автономном режиме) и удаляет свой прослушиватель после успешного извлечения DataSnapshot
ONCE (офлайн или онлайн)?
Ответы
Ответ 1
Как работает упорство
Клиент Firebase хранит копию всех данных, которые вы активно слушаете в памяти. Когда последний прослушиватель отключается, данные сбрасываются из памяти.
Если вы включили сохранение на жестком диске в приложении Firebase для Android с помощью
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()
снова, если произошла смена данных на сервере с момента последнего добавления в кэш.
Что произойдет, если вы используете addListenerForSingleValueEvent
Когда вы добавляете прослушиватель событий одного значения в одно и то же место:
ref.addListenerForSingleValueEvent(listener);
Клиент Firebase (как в предыдущей ситуации) сразу вызывает onDataChange()
для значения из кеша локального диска. Он больше не будет вызывать onDataChange()
, даже если значение на сервере оказывается другим. Обратите внимание, что обновленные данные по-прежнему будут запрашиваться и возвращаться при последующих запросах.
Это было рассмотрено ранее в Как работает синхронизация Firebase с общими данными?
Решение и обходное решение
Лучшим решением является использование addValueEventListener()
вместо однозначного прослушивателя событий. Регулярный слушатель значений получит как локальное событие, так и потенциальное обновление с сервера.
В качестве обходного пути вы также можете вызвать keepSynced(true)
в местах, где используется однозначный прослушиватель событий. Это гарантирует, что данные обновляются каждый раз, когда они меняются, что значительно повышает вероятность того, что ваш однозначный прослушиватель событий увидит текущее значение.
Ответ 2
Вы можете создать транзакцию и прервать ее, затем onComplete будет вызываться в режиме онлайн (данные nline) или в автономном режиме (кэшированные данные)
Я ранее создал функцию, которая работала только в том случае, если база данных получила соединение, достаточное для синхронизации. Я исправил проблему, добавив тайм-аут. Я буду работать над этим и проверить, работает ли это. Может быть, в будущем, когда я получу свободное время, я создам андроидскую библиотеку и опубликую ее, но к тому времени это код в kotlin:
/**
* @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");
}
});
Ответ 3
Когда workinkg with persistence включен, я подсчитал время, когда слушатель получил вызов onDataChange() и остановился для прослушивания в 2 раза. Работал для меня, может быть, помогает:
private int timesRead;
private ValueEventListener listener;
private DatabaseReference ref;
private void readFB() {
timesRead = 0;
if (ref == null) {
ref = mFBDatabase.child("URL");
}
if (listener == null) {
listener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
//process dataSnapshot
timesRead++;
if (timesRead == 2) {
ref.removeEventListener(listener);
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
};
}
ref.removeEventListener(listener);
ref.addValueEventListener(listener);
}