2015-12-28 18 views
44

Ilekroć używam addListenerForSingleValueEvent z setPersistenceEnabled(true), ja tylko udało się uzyskać lokalną kopię offline DataSnapshot i NIE zaktualizowanego DataSnapshot z serwera.Firebase Offline Możliwości i addListenerForSingleValueEvent

Jednak jeśli użyję addValueEventListener z setPersistenceEnabled(true), mogę uzyskać najnowszą kopię DataSnapshot z serwera.

Czy to normalne dla addListenerForSingleValueEvent ponieważ tylko przeszukuje DataSnapshot lokalnie (offline) i usuwa jego słuchacza po pomyślnym pobraniu DataSnapshotRAZ (w trybie offline lub online)?

Odpowiedz

57

Jak działa utrzymywanie

Klient Firebase przechowuje kopię wszystkich danych masz aktywnego słuchania w pamięci. Po rozłączeniu ostatniego słuchacza dane są usuwane z pamięci.

Jeśli włączysz trwałość dysku w aplikacji Android z Firebase:

Firebase.getDefaultConfig().setPersistenceEnabled(true); 

Klient Firebase będzie utrzymywać lokalną kopię (na dysku) wszystkich danych, że aplikacja została niedawno słuchałem.

Co się dzieje, kiedy dołączyć słuchacza

powiedzieć, że mają następujący ValueEventListener:

ValueEventListener listener = new ValueEventListener() { 
    @Override 
    public void onDataChange(DataSnapshot snapshot) { 
     System.out.println(snapshot.getValue()); 
    } 

    @Override 
    public void onCancelled(FirebaseError firebaseError) { 
     // No-op 
    } 
}; 

Po dodaniu ValueEventListener do lokalizacji:

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

Jeśli wartość z lokalizacja znajduje się w lokalnej pamięci podręcznej dysku, klient Firebase natychmiast wywoła numer onDataChange() dla tej wartości z lokalnej pamięci podręcznej. Jeśli następnie zainicjuje czek z serwerem, poprosi o aktualizację wartości. To może ponownie wywołać onDataChange() ponownie, jeśli nastąpiła zmiana danych na serwerze, ponieważ została ostatnio dodana do pamięci podręcznej.

Co się dzieje, gdy używasz addListenerForSingleValueEvent

Podczas dodawania zdarzeń słuchacza pojedyncza wartość w tej samej lokalizacji:

ref.addListenerForSingleValueEvent(listener); 

Klient Firebase będzie (jak w poprzedniej sytuacji) niezwłocznie powołać onDataChange() dla wartość z lokalnej pamięci podręcznej dysku. Będzie to , a nie wywoływać onDataChange() więcej razy, nawet jeśli wartość na serwerze okaże się inna. Zwróć uwagę, że zaktualizowane dane będą nadal wymagane i zwrócone przy kolejnych żądaniach.

ten pokryto uprzednio w How does Firebase sync work, with shared data?

roztworu i obejście

Najlepszym rozwiązaniem jest użycie addValueEventListener(), zamiast detektora zdarzenia pojedynczych wartości. Program nasłuchujący o wartości normalnej otrzyma zarówno natychmiastowe zdarzenie lokalne, jak i potencjalną aktualizację z serwera.

Jako obejście można również call keepSynced(true) w lokalizacjach, w których używany jest odbiornik zdarzeń o pojedynczej wartości. Zapewnia to aktualizację danych po każdej zmianie, co drastycznie zwiększa szansę, że detektor zdarzeń o pojedynczej wartości zobaczy aktualną wartość.

+2

Dziękuję. Teraz rozumiem odpowiedź z linku. –

+3

Dzięki za perfekcyjną ilustrację. Ale keepSynced (true) i addValueEventListener będą utrzymywać otwarte połączenie przez cały czas. W przeciwieństwie do keepSynced (false) i addListenerForSingleValueEvent pozwoli firebase na rozłączenie się po pewnym czasie. Jak mogę wymusić ręczną aktualizację? –

+0

To niewygodne zachowanie sprawia, że ​​testowanie jest prawie niemożliwe. –

0

Można utworzyć transakcję i przerwać go, a następnie onComplete zostanie wywołana, gdy w Internecie (dane nLinia) lub offline (buforowane dane)

ja poprzednio funkcję, która pracowała tylko wtedy, gdy baza danych dostał połączenie lomng wystarczy zrobić synchronizację utworzony. Naprawiłem problem przez dodanie limitu czasu. Popracuję nad tym i sprawdzę, czy to działa. Być może w przyszłości, kiedy się wolny czas, będę tworzyć android lib i publikuje je, ale przez to jest kod w 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) 
      } 
     } 

W kodzie jeśli raz się jest ustawiony następnie skonfigurować stoper który wywoła transakcję z abortem. Ta transakcja zostanie wywołana nawet w trybie offline i zapewni dane online lub buforowane (w tej funkcji istnieje naprawdę duża szansa, że ​​dane te zostaną zapisane w pamięci podręcznej). Następnie wywołuję transakcję z powodzeniem. OnComplete zostanie wywołany TYLKO, jeśli otrzymamy odpowiedź z bazy danych Firebase. Możemy teraz anulować timer (jeśli nie jest zerowy) i wysłać dane do wywołania zwrotnego.

Ta implementacja sprawia, że ​​99% użytkowników ma pewność, że dane pochodzą z pamięci podręcznej lub są dostępne online.

Jeśli chcesz zrobić to szybciej w trybie offline (aby nie czekać głupio z limitu czasu, gdy baza danych nie jest oczywiście podłączony), a następnie sprawdzić, czy baza danych jest podłączony przed użyciem funkcji powyżej:

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

Kiedy workinkg z Utrwalenie włączone, policzyłem czasy, w których słuchacz odebrał wywołanie onDataChange() i zatrzymał się na 2-krotne słuchanie. Pracował dla mnie, może pomaga:

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