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

Как обойти кеш Firebase для обновления данных (в приложении Android)?

В приложении Android, которое в большинстве случаев должно работать в автономном режиме, когда мне нужно, выполнять некоторые синхронные операции, например:

User myUser =  MyclientFacade.getUser();
If (myUser.getScore > 10) {
    DoSomething() 
}

Где Пользователь POJO, заполненный Firebase;

Проблема возникает при активации кеша Firebase

Firebase.getDefaultConfig().setPersistenceEnabled(true);

и пользователь уже находится в кеше, и данные обновляются в Firebase DB третьей стороной (или даже другим устройством). Действительно, когда я запрашиваю Firebase, чтобы получить User, я сначала получаю данные из кэша, а затем второе событие изменения с последними данными с сервера Firebase, но это слишком поздно!

Посмотрим на синхронный метод MyclientFacade.getUser():

Public User  getUser()  {
  Firebase ref = myFireBaseroot.child("User").child(uid);
  ref.keepSynced(true);
  /* try {
    Thread.sleep(3000);
 } catch (InterruptedException e) {
    e.printStackTrace();
 }*/
final CountDownLatch signal = new CountDownLatch(1);

ref.addListenerForSingleValueEvent(new ValueEventListener() {
//ref.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
       this.user = dataSnapshot.getValue(User.class);
       signal.countDown();
    }
    @Override
    public void onCancelled(FirebaseError firebaseError) {
       signal.countDown();
    }
});
signal.await(5, TimeUnit.SECONDS);
ref.keepSynced(false);
return this.user;
}

Я получаю такое же поведение, если я использую addValueEventListener или addListenerForSingleValueEvent, смешанный с ref.keepSynced:

Скажем, значение моего пользователя в кеше равно 5, а из Firebase DB - 11.

Когда я звоню getUser, я получу оценку 5 (сначала запросить кеш Firebase), поэтому я не буду называть метод doSomething().

Если я раскомментирую код Thread.sleep() из моего примера, кеш Firebase будет иметь достаточно времени для обновления, а мой getUser вернет правильное значение оценки (11).

Итак, как я могу напрямую задать последнее значение непосредственно со стороны сервера и обойти кеш?

4b9b3361

Ответ 1

Это была проблема, которая вызывала у меня стресс и в моем приложении.

Я пробовал все: от изменения .addListenerForSingleValueEvent() до .addValueEventListener() до попытки творчески использовать .keepSynced() для использования задержки (метод Thread.sleep(), описанный выше), и ничего действительно не работало последовательно (даже Thread.sleep() метод, который не был действительно приемлем в производственном приложении, не дал мне последовательных результатов).

Итак, я сделал это: после создания объекта Query и вызова .keepSynced() на нем я затем начал писать объект mock/token в node, который я запрашиваю и THEN в этом слушателе завершения операции, я делаю поиск данных, который я хочу сделать, после удаления макетного объекта.

Что-то вроде:

 MockObject mock = new MockObject();
    mock.setIdentifier("delete!");

    final Query query = firebase.child("node1").child("node2");

    query.keepSynced(true);

    firebase.child("node1").child("node2").child("refreshMock")
            .setValue(mock, new CompletionListener() {

                @Override
                public void onComplete(FirebaseError error, Firebase afb) {

                    query.addListenerForSingleValueEvent(new ValueEventListener() {

                        public void onDataChange(DataSnapshot data) {

                            // get identifier and recognise that this data
                            // shouldn't be used
                            // it also probably a good idea to remove the
                            // mock object
                            // using the removeValue() method in its
                            // speficic node so
                            // that the database won't be saddled with a new
                            // one in every
                            // read operation

                        }

                        public void onCancelled(FirebaseError error) {
                        }

                    });

                }

            });
}

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

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

Надеюсь, это поможет!

Ответ 2

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

String firebaseUrl = "/some/user/datapath/";
final Firebase firebaseClient = new Firebase(firebaseUrl);

    // Use runTransaction to bypass cached DataSnapshot
    firebaseClient.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            // Return passed in data
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(FirebaseError firebaseError, boolean success, DataSnapshot dataSnapshot) {
            if (firebaseError != null || !success || dataSnapshot == null) {
              System.out.println("Failed to get DataSnapshot");
            } else {
              System.out.println("Successfully get DataSnapshot");
              //handle data here
            }
        }
    });

Ответ 3

Моим решением было вызвать Database.database(). isPersistenceEnabled = true при загрузке, а затем вызвать .keepSynced(true) на любых узлах, которые мне нужно обновить.

Проблема в том, что если вы запросите свой node сразу после .keepSynced(true), вы скорее всего получите кеш, а не свежие данные. Немного хромая, но функциональная работа: задерживайте запрос node на секунду или около того, чтобы дать Firebase некоторое время для получения новых данных. Вместо этого вы получите кеш, если пользователь отключен.

О, и если это node, что вы не хотите постоянно обновляться в фоновом режиме навсегда, не забудьте позвонить .keepSynced(false), когда закончите.

Ответ 4

Я попробовал как принятое решение, так и попытку транзакции. Решение транзакции более чистое и приятное, но успех OnComplete вызывается только тогда, когда db находится в сети, поэтому вы не можете загружать из кеша. Но вы можете прервать транзакцию, тогда onComplete будет вызываться в автономном режиме (с кэшированными данными).

Я ранее создал функцию, которая работала только в том случае, если база данных получила соединение, достаточное для синхронизации. Я исправил проблему, добавив тайм-аут. Я буду работать над этим и проверить, работает ли это. Может быть, в будущем, когда я получу свободное время, я создам андроидскую библиотеку и опубликую ее, но к тому времени это код в 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)
                removeListener()
            }

            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)
                removeListener()
            }

            override fun doTransaction(p0: MutableData?): Transaction.Result {
                return Transaction.success(p0)
            }
        }

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

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");
  }
});

Ответ 5

Просто добавьте этот код в метод onCreate в свой класс . (изменить ссылку базы данных)

Пример:

public class MyApplication extendes Application{

 @Override
    public void onCreate() {
        super.onCreate();
            DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
            scoresRef.keepSynced(true);
      }
}

Хорошо работает для меня.

Ссылка: https://firebase.google.com/docs/database/android/offline-capabilities