web-dev-qa-db-fra.com

Combinaison du programme d'écoute de données en temps réel Firebase avec RxJava

J'utilise Firebase dans mon application, avec RxJava . Firebase est capable de notifier votre application chaque fois que quelque chose a changé dans les données principales (ajout, suppressions, modifications, ...) . Je suis essayant de combiner la fonctionnalité de Firebase avec RxJava.

Les données que j'écoute s'appellent Leisure et la Observable émet LeisureUpdate qui contient une Leisure et le type de mise à jour (ajout, suppression, déplacement, modification).

Voici ma méthode qui permet de s'abonner à ces événements.

private Observable<LeisureUpdate> leisureUpdatesObservable;
private ChildEventListener leisureUpdatesListener;
private int leisureUpdatesSubscriptionsCount;

@NonNull
public Observable<LeisureUpdate> subscribeToLeisuresUpdates() {
    if (leisureUpdatesObservable == null) {
        leisureUpdatesObservable = Observable.create(new Observable.OnSubscribe<LeisureUpdate>() {

            @Override
            public void call(final Subscriber<? super LeisureUpdate> subscriber) {
                leisureUpdatesListener = firebase.child(FirebaseStructure.LEISURES).addChildEventListener(new ChildEventListener() {
                    @Override
                    public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                        final Leisure leisure = convertMapToLeisure((Map<String, Object>) dataSnapshot.getValue());
                        subscriber.onNext(new LeisureUpdate(leisure, LeisureUpdate.ADDED));
                    }

                    @Override
                    public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                        final Leisure leisure = convertMapToLeisure((Map<String, Object>) dataSnapshot.getValue());
                        subscriber.onNext(new LeisureUpdate(leisure, LeisureUpdate.CHANGED));
                    }

                    @Override
                    public void onChildRemoved(DataSnapshot dataSnapshot) {
                        final Leisure leisure = convertMapToLeisure((Map<String, Object>) dataSnapshot.getValue());
                        subscriber.onNext(new LeisureUpdate(leisure, LeisureUpdate.REMOVED));
                    }

                    @Override
                    public void onChildMoved(DataSnapshot dataSnapshot, String s) {
                        final Leisure leisure = convertMapToLeisure((Map<String, Object>) dataSnapshot.getValue());
                        subscriber.onNext(new LeisureUpdate(leisure, LeisureUpdate.MOVED));
                    }

                    @Override
                    public void onCancelled(FirebaseError firebaseError) {
                        subscriber.onError(new Error(firebaseError.getMessage()));
                    }
                });
            }
        });
    }
    leisureUpdatesSubscriptionsCount++;
    return leisureUpdatesObservable;
}

Tout d'abord, j'aimerais utiliser la méthode Observable.fromCallable() afin de créer la Observable, mais je suppose que c'est impossible, puisque Firebase utilise des rappels, n'est-ce pas?

Je garde une seule instance de Observable afin d'avoir toujours une Observable où plusieurs Subscriber peuvent s'abonner.

Le problème survient lorsque tout le monde se désabonne et que je dois cesser d'écouter les événements dans Firebase . Je n'ai de toute façon pas réussi à faire comprendre à la Observable s'il y a encore un abonnement. Je continue donc à compter le nombre d'appels auxquels je suis arrivé à subscribeToLeisuresUpdates(), avec leisureUpdatesSubscriptionsCount.

Ensuite, chaque fois que quelqu'un veut se désabonner, il doit appeler

@Override
public void unsubscribeFromLeisuresUpdates() {
    if (leisureUpdatesObservable == null) {
        return;
    }
    leisureUpdatesSubscriptionsCount--;
    if (leisureUpdatesSubscriptionsCount == 0) {
        firebase.child(FirebaseStructure.LEISURES).removeEventListener(leisureUpdatesListener);
        leisureUpdatesObservable = null;
    }
}

C’est le seul moyen que j’ai trouvé pour faire en sorte que la Observable émette des éléments s’il ya un abonné, mais j’ai le sentiment qu’il doit exister un moyen plus simple, particulièrement de comprendre quand aucun autre abonné n’écoute l'observable.

Toute personne ayant rencontré un problème similaire ou ayant une approche différente? 

12
Daniele Vitali

Vous pouvez utiliser Observable.fromEmitter, quelque chose dans ce sens

    return Observable.fromEmitter(new Action1<Emitter<LeisureUpdate>>() {
        @Override
        public void call(final Emitter<LeisureUpdate> leisureUpdateEmitter) {
            final ValueEventListener listener = new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    // process update
                    LeisureUpdate leisureUpdate = ...
                    leisureUpdateEmitter.onNext(leisureUpdate);
                }

                @Override
                public void onCancelled(DatabaseError databaseError) {
                    leisureUpdateEmitter.onError(new Throwable(databaseError.getMessage()));
                    mDatabaseReference.removeEventListener(this);
                }
            };
            mDatabaseReference.addValueEventListener(listener);
            leisureUpdateEmitter.setCancellation(new Cancellable() {
                @Override
                public void cancel() throws Exception {
                    mDatabaseReference.removeEventListener(listener);
                }
            });
        }
    }, Emitter.BackpressureMode.BUFFER);
6
Francesc

Je vous suggère de vérifier (ou tout simplement de l'utiliser) l'une des bibliothèques suivantes:

RxJava: https://github.com/nmoskalenko/RxFirebase

RxJava 2.0: https://github.com/FrangSierra/Rx2Firebase

L'un d'eux fonctionne avec RxJava et l'autre avec le nouveau RC de RxJava 2.0. Si vous êtes intéressé, vous pouvez voir les différences entre les deux ici .

Mettez ceci dans votre Observable.create () à la fin. 

subscriber.add(Subscriptions.create(new Action0() {
                    @Override public void call() {
                        ref.removeEventListener(leisureUpdatesListener);
                    }
                }));
3
Vinay Nagaraj

Une autre bibliothèque étonnante qui vous aidera à envelopper toute la logique de base de données en temps réel de firebase sous des modèles rx.

https://github.com/Link184/Respiration

Ici, vous pouvez créer votre référentiel firebase et l'étendre à partir de GeneralRepository, par exemple:

@RespirationRepository(dataSnapshotType = Leisure.class)
public class LeisureRepository extends GeneralRepository<Leisure>{
    protected LeisureRepository(Configuration<Leisure> repositoryConfig) {
        super(repositoryConfig);
    }

    // observable will never emit any events if there are no more subscribers
    public void killRepository() {
        if (!behaviorSubject.hasObservers()) {
            //protected fields
            behaviorSubject.onComplete();
            databaseReference.removeEventListener(valueListener);
        }
    }
}

Vous pouvez "tuer" votre dépôt de cette manière:

// LeisureRepositoryBuilder is a generated class by annotation processor, will appear after a successful gradle build
LeisureRepositoryBuilder.getInstance().killRepository();

Mais je pense que votre situation sera préférable d’étendre com.link184.respiration.repository.ListRepository pour éviter le mappage des données de Java.util.Map au modèle Leisure via LeisureUpdate

0
Link182

Voici un exemple de code d'utilisation de RxJava2 avec CompletionListener de Firebase:

Completable.create(new CompletableOnSubscribe() {
        @Override
        public void subscribe(final CompletableEmitter e) throws Exception {
            String orderKey = FirebaseDatabase.getInstance().getReference().child("orders").Push().getKey();
            FirebaseDatabase.getInstance().getReference().child("orders").child(orderKey).setValue(order,
                    new DatabaseReference.CompletionListener() {
                        @Override
                        public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                            if (e.isDisposed()) {
                                return;
                            }
                            if (databaseError == null) {
                                e.onComplete();
                            } else {
                                e.onError(new Throwable(databaseError.getMessage()));
                            }
                        }
                    });

        }
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
0
granko87