Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 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);
}