web-dev-qa-db-fra.com

Fonctionnalités hors ligne de Firebase et addListenerForSingleValueEvent

Chaque fois que j'utilise addListenerForSingleValueEvent avec setPersistenceEnabled(true), je ne parviens qu'à obtenir une copie hors ligne locale de DataSnapshot etNOTla version mise à jour DataSnapshot du serveur.

Cependant, si j'utilise addValueEventListener avec setPersistenceEnabled(true), je peux obtenir la dernière copie de DataSnapshot sur le serveur.

Est-ce normal pour addListenerForSingleValueEvent car elle ne fait que rechercher DataSnapshot localement (hors connexion) et supprime son écouteur après avoir récupéré avec succès DataSnapshotUNE FOIS(hors ligne ou en ligne)?

51
Jason Hoch

Comment fonctionne la persistance

Le client Firebase conserve en mémoire une copie de toutes les données que vous écoutez activement. Une fois le dernier écouteur déconnecté, les données sont effacées de la mémoire.

Si vous activez la persistance de disque dans une application Firebase Android avec:

Firebase.getDefaultConfig().setPersistenceEnabled(true); 

Le client Firebase conservera une copie locale (sur le disque) de toutes les données récemment écoutées par l'application.

Que se passe-t-il lorsque vous attachez un auditeur?

Supposons que vous ayez la ValueEventListener suivante:

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

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

Lorsque vous ajoutez une ValueEventListener à un emplacement:

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

Si la valeur de l'emplacement se trouve dans le cache de disque local, le client Firebase appellera immédiatement onDataChange() pour cette valeur à partir du cache local. Si sera également initier une vérification avec le serveur, pour demander des mises à jour de la valeur. Il peut invoquer par la suite à nouveau onDataChange() s'il y a eu un changement des données sur le serveur depuis leur dernière insertion dans le cache.

Que se passe-t-il lorsque vous utilisez addListenerForSingleValueEvent

Lorsque vous ajoutez un écouteur d'événement à valeur unique au même emplacement:

ref.addListenerForSingleValueEvent(listener);

Comme dans la situation précédente, le client Firebase appellera immédiatement onDataChange() pour la valeur du cache de disque local. Not n'appellera plus la onDataChange(), même s'il s'avère que la valeur sur le serveur est différente. Notez que les données mises à jour seront toujours demandées et renvoyées lors des demandes suivantes.

Cela a été couvert précédemment dans Comment fonctionne la synchronisation Firebase avec les données partagées?

Solution et solution de contournement

La meilleure solution consiste à utiliser addValueEventListener() , au lieu d’un écouteur d’événements à valeur unique. Un écouteur à valeur normale obtiendra l'événement local immédiat et la mise à jour potentielle du serveur.

En guise de solution de contournement, vous pouvez également appeler keepSynced(true) sur les emplacements où vous utilisez un écouteur d'événement à valeur unique. Cela garantit que les données sont mises à jour chaque fois qu'elles changent, ce qui améliore considérablement les chances que votre écouteur d'événements à valeur unique voie la valeur actuelle.

66
Frank van Puffelen

Vous pouvez créer une transaction et l’abandonner. OnComplete sera appelée en ligne (données nline) ou hors ligne (données en mémoire cache).

J'ai précédemment créé une fonction qui ne fonctionnait que si la base de données disposait d'une connexion suffisante pour effectuer la synchronisation. J'ai résolu le problème en ajoutant timeout. Je vais travailler sur cela et tester si cela fonctionne. Peut-être que dans le futur, quand je disposerai de mon temps libre, je créerai une librairie Android et la publierai, mais d'ici là, c'est le code en 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)
            }
        }

Dans le code, si le délai est défini, je configure le chronomètre qui appelle la transaction en cas d’abandon. Cette transaction sera appelée même en mode hors connexion et fournira des données en ligne ou en cache (dans cette fonction, il y a de fortes chances pour que ces données soient en cache) ..... J'appelle alors transaction avec succès. OnComplete sera appelé UNIQUEMENT si nous obtenons une réponse de la base de données firebase. Nous pouvons maintenant annuler le chronomètre (si non nul) et envoyer des données à rappeler.

Cette implémentation garantit à 99% que les données proviennent du cache ou sont en ligne. 

Si vous voulez accélérer le processus hors connexion (pour ne pas attendre stupidement avec un délai d'expiration alors que la base de données n'est pas connectée), vérifiez si la base de données est connectée avant d'utiliser la fonction ci-dessus:

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
Janusz Hain

Lorsque workinkg avec la persistance activée, j'ai compté le nombre de fois où l'auditeur a reçu un appel à onDataChange () et arrêté de m'écouter à deux reprises. Travaillé pour moi, aide peut-être:

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