2015-12-28 16 views
44

Ogni volta che uso addListenerForSingleValueEvent con setPersistenceEnabled(true), riesco solo per ottenere una copia non in linea locale del DataSnapshot e NON aggiornato DataSnapshot dal server.Firebase Offline Capacità e addListenerForSingleValueEvent

Tuttavia, se uso addValueEventListener con setPersistenceEnabled(true), è possibile ottenere l'ultima copia di DataSnapshot dal server.

È questo normale per addListenerForSingleValueEvent in quanto cerca solo DataSnapshot a livello locale (offline) e rimuove il suo ascoltatore dopo il recupero con successo DataSnapshotVOLTA (sia offline o online)?

risposta

57

Come funziona la persistenza

Il cliente Firebase mantiene una copia di tutti i dati si sta attivamente ascoltando in memoria. Quando l'ultimo ascoltatore si disconnette, i dati vengono scaricati dalla memoria.

Se si abilita la persistenza su disco in un'applicazione Android con Firebase:

Firebase.getDefaultConfig().setPersistenceEnabled(true); 

Il cliente Firebase manterrà una copia locale (su disco) di tutti i dati che l'applicazione ha recentemente ascoltato.

Cosa succede quando si collega un ascoltatore

Diciamo che avete il seguente ValueEventListener:

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

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

Quando si aggiunge un ValueEventListener in una posizione:

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

Se il valore della la posizione è nella cache del disco locale, il client Firebase invocherà immediatamente onDataChange() per quel valore dalla cache locale. Se poi avvierà anche un controllo con il server, per chiedere eventuali aggiornamenti del valore. Lo può successivamente richiamare onDataChange() di nuovo se c'è stato un cambiamento dei dati sul server dall'ultima volta che è stato aggiunto alla cache.

Cosa succede quando si utilizza addListenerForSingleValueEvent

Quando si aggiunge un listener di eventi singolo valore nella stessa posizione:

ref.addListenerForSingleValueEvent(listener); 

Il client Firebase sarà (come nella situazione precedente) immediatamente richiamare onDataChange() per la valore dalla cache del disco locale. Sarà non invocare lo onDataChange() più volte, anche se il valore sul server risulta diverso. Si noti che i dati aggiornati verranno comunque richiesti e restituiti in caso di richieste successive.

Questo era coperto precedentemente in How does Firebase sync work, with shared data?

Solution e soluzione

La soluzione migliore è quella di utilizzare addValueEventListener(), invece di un ascoltatore evento singolo valore. Un listener di valori regolari otterrà sia l'evento locale immediato che il potenziale aggiornamento dal server.

Come soluzione alternativa è possibile anche call keepSynced(true) nelle posizioni in cui si utilizza un listener di eventi a valore singolo. Ciò garantisce che i dati vengano aggiornati ogni volta che cambia, il che aumenta drasticamente la possibilità che il listener di eventi a valore singolo visualizzi il valore corrente.

+2

Grazie. Capisco ora dalla risposta del link .. –

+3

Grazie per l'illustrazione perfetta. Ma keepSynced (true) e addValueEventListener manterranno una connessione aperta tutto il tempo. Al contrario di keepSynced (false) e addListenerForSingleValueEvent consentirà al firebase di disconnettersi in seguito. Come posso forzare un aggiornamento manuale una volta? –

+0

Che comportamento sconveniente, rende quasi impossibile la verifica. –

0

È possibile creare delle transazioni e abortire, poi onComplete saranno chiamati quando online (dati nline) o offline (dati memorizzati nella cache)

ho precedentemente funzione che funzionava solo se il database ottenuto collegamento lomng abbastanza da fare synch creato. Ho risolto il problema aggiungendo il timeout. Lavorerò su questo e testerò se funziona. Forse in futuro, quando avrò tempo libero, creerò lib Android e di pubblicarla, ma ormai è il codice in 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) 
      } 
     } 

Nel codice se il tempo di uscita viene impostata poi ho impostato il timer che chiamerà la transazione con l'interruzione. Questa transazione verrà chiamata anche quando non in linea e fornirà dati in linea o memorizzati nella cache (in questa funzione è molto probabile che questi dati siano memorizzati nella cache). Poi chiamo transazione con successo. OnComplete verrà chiamato SOLO se otteniamo risposta dal database di Firebase. Ora possiamo annullare il timer (se non è null) e inviare i dati al callback.

Questa implementazione rende sicuro al 99% che i dati provengano dalla cache o siano in linea.

Se si vuole rendere più rapida in linea (per non aspettare stupidamente con timeout quando, ovviamente, di database non è collegato) quindi verificare se il database è collegato prima di utilizzare la funzione di cui sopra:

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

Quando workinkg con persistenza abilitata, ho contato le volte in cui il listener ha ricevuto una chiamata a onDataChange() e si è fermata ad ascoltare 2 volte. Ha funzionato per me, forse aiuta:

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