2016-02-17 3 views
26

На Android приложение, которое должно работает в автономном режиме большую часть времени мне нужно, когда это в Интернете, чтобы сделать некоторые синхронные операции на то:Как обойти кеш Firebase для обновления данных (в Android-приложении)?

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

Где Пользователь является POJO заполнен Firebase;

Проблема возникает, когда кэш Firebase активируется

Firebase.getDefaultConfig().setPersistenceEnabled(true); 

и пользователь уже находится в кэше, и данные обновляются на Firebase БД третьей стороной (или даже на другом устройстве). Действительно, когда я запрашиваю 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. 11.

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

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

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

+0

Также на: https://groups.google.com/forum/#!topic/firebase- talk/jFnO-QXKSwA –

+1

См. http://stackoverflow.com/questions/34486417/firebase-offline-capabilities-and-addlistenerforsinglevalueevent, http://stackoverflow.com/questions/33260450/how-does-work-firebase-sync -w i-share-data и https://groups.google.com/forum/#!msg/firebase-talk/ptTtEyBDKls/XbNKD_K8CQAJ –

+0

К сожалению, темы, которые вы опубликовали, не дают удобного ответа. Даже если я использую классический «addValueEventListener», у меня та же проблема, что и на «onDataChange (DataSnapshot data) {....}»: ** нет способа узнать, пришли ли данные из кеша или из сети, и, следовательно, если она обновлена ​​**; так что невозможно доверие к значению данных при синхронном лечении. – toofoo

ответ

23

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

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

Так что я сделал это: после создания запроса объекта и вызова .keepSynced() на него, я затем перейти написать макет/маркер объект в узле Я запросы и ТОГДА в завершении слушателя, что операция, я сделайте поиск данных, который я хочу сделать, после удаления макетного объекта.

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

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's 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) { 
         } 

        }); 

       } 

      }); 
} 

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

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

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

+0

Это звучит неплохо обходным путем из-за отсутствия чего-либо лучшего. Я не могу проверить это решение сейчас, но постараюсь, спасибо ...... – toofoo

+3

Я действительно не понимаю, почему Firebase архивируется так странно. Нет ли способа сделать это? :/ –

+0

еще один вопрос к Antonis427: вы проверили, что произойдет, если вы на мгновение (или полностью) отключились, пока вы сохраняете макет? – toofoo

13

Обходной путь, который я обнаружил, использует метод 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 
      } 
     } 
    }); 
+0

, мне нужно его протестировать (а также проверить поведение в автономном режиме в этом случае ....) – toofoo

+2

Это работает, но если вы «он не будет завершен, пока вы не вернетесь в онлайн. Закончилось завершение его в тайм-аут и возврат к addListenerForSingleValueEvent и отбрасывание значения, когда он снова подключен к сети. –

+0

Как вы думаете, я мог бы также использовать этот метод «runtransaction» для запроса большого списка (вместо использования ChildEventListener)? – toofoo

1

Просто добавьте этот код на 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

+0

это работает только для синхронизации узла «оценка». При написании mockObject позволяют запрашивать «свежие» данные на любом узле – toofoo

+0

Да, но если вы хотите, чтобы любой узел был синхронизирован, просто удалите Firebase.getDefaultConfig(). SetPersistenceEnabled (true); В моем случае я хотел сохранить только один узел, синхронизированный. –

+0

Это нехорошее решение, потому что вы слушаете тогда КАЖДОЕ изменение данных на узле. Если у вас есть, например, дочерние узлы - каждый для другого пользователя, то вы получите большой объем данных. Одно изменение в данных одного пользователя приведет к синхронизации данных для каждого другого пользователя, который слушает этот узел. Вместо этого лучше включать и выключать keepSynced только для действительно необходимых данных. –

2

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

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

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

+0

Некоторые узлы могут быть синхронизированы все время (текущие пользовательские), но проблема заключается в том, «когда прекратить синхронизацию» в вашем решении. Отмеченный ответ корректен - вы можете сделать одну простую операцию (я использую removeValue на несуществующем узле), синхронизируя вас, в onComplete вы можете отключить синхронизацию, поскольку она уже синхронизирована. Гораздо проще сделать синхронизацию безопасным –

+0

Спасибо за обратную связь :) Если это правда, что использование removeValue на бессмысленном узле заставляет синхронизацию (я ее еще не пробовал), то это кажется хорошим решением. Написание, а затем удаление значения, как и в принятом ответе, показалось мне немного ужасным. По правде говоря, все это немного взломанно, и я был очень удивлен, узнав, что так работает Firebase sync. –

+0

В моих решениях удалялся не существующий узел. Да, это все еще взломать, мне нужно подумать с моими коллегами, что делать, может быть, мы отключим автономную базу данных, это будет очень проблематично печально. –

2

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

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

/** 
    * @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"); 
    } 
}); 
Смежные вопросы