268 votes

Quand utiliser RxJava Observable et quand utiliser Callback sur Android ?

Je travaille sur la mise en réseau de mon application. J'ai donc décidé d'essayer l'application Square Rétrofit . Je vois qu'ils soutiennent les simples Callback

@GET("/user/{id}/photo")
void getUserPhoto(@Path("id") int id, Callback<Photo> cb);

et de RxJava Observable

@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);

Les deux se ressemblent à première vue, mais lorsqu'il s'agit de les mettre en œuvre, cela devient intéressant...

Alors qu'avec une implémentation de callback simple, cela ressemblerait à ceci :

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess() {
    }
});

ce qui est assez simple et direct. Et avec Observable cela devient vite verbeux et assez compliqué.

public Observable<Photo> getUserPhoto(final int photoId) {
    return Observable.create(new Observable.OnSubscribeFunc<Photo>() {
        @Override
        public Subscription onSubscribe(Observer<? super Photo> observer) {
            try {
                observer.onNext(api.getUserPhoto(photoId));
                observer.onCompleted();
            } catch (Exception e) {
                observer.onError(e);
            }

            return Subscriptions.empty();
        }
    }).subscribeOn(Schedulers.threadPoolForIO());
}

Et ce n'est pas tout. Tu dois encore faire quelque chose comme ça :

Observable.from(photoIdArray)
        .mapMany(new Func1<String, Observable<Photo>>() {
            @Override
            public Observable<Photo> call(Integer s) {
                return getUserPhoto(s);
            }
        })
        .subscribeOn(Schedulers.threadPoolForIO())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Action1<Photo>() {
            @Override
            public void call(Photo photo) {
                //save photo?
            }
        });

Est-ce que je rate quelque chose ici ? Ou est-ce un mauvais cas pour utiliser Observable s ? Quand préférerait-on/devrait-on Observable sur un simple Callback ?

Mise à jour

L'utilisation du rétrofit est beaucoup plus simple que l'exemple ci-dessus, comme @Niels l'a montré dans sa réponse ou dans l'exemple de projet de Jake Wharton. U2020 . Mais au fond, la question reste la même : quand faut-il utiliser l'une ou l'autre méthode ?

0 votes

Pouvez-vous mettre à jour votre lien vers le fichier dont vous parlez dans U2020

0 votes

Il fonctionne toujours...

6 votes

Mec, j'ai eu exactement les mêmes pensées quand j'ai lu que RxJava était la nouvelle chose. J'ai lu un exemple de rétrofit (parce que je suis extrêmement familier avec ça) d'une requête simple et c'était dix ou quinze lignes de code et ma première réaction a été : tu dois te moquer de moi =/ . Je n'arrive pas non plus à comprendre comment cela remplace un bus d'événements, car le bus d'événements vous découple de l'observable et rxjava réintroduit le couplage, sauf erreur de ma part.

359voto

Niels Points 742

Pour le simple travail en réseau, les avantages de RxJava par rapport à Callback sont très limités. L'exemple simple de getUserPhoto :

RxJava :

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
            @Override
            public void call(Photo photo) {
               // do some stuff with your photo 
            }
     });

Rappel :

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess(Photo photo, Response response) {
    }
});

La variante RxJava n'est pas beaucoup mieux que la variante Callback. Pour l'instant, ignorons la gestion des erreurs. Prenons une liste de photos :

RxJava :

api.getUserPhotos(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<List<Photo>, Observable<Photo>>() {
    @Override
    public Observable<Photo> call(List<Photo> photos) {
         return Observable.from(photos);
    }
})
.filter(new Func1<Photo, Boolean>() {
    @Override
    public Boolean call(Photo photo) {
         return photo.isPNG();
    }
})
.subscribe(
    new Action1<Photo>() {
    @Override
        public void call(Photo photo) {
            list.add(photo)
        }
    });

Rappel :

api.getUserPhotos(userId, new Callback<List<Photo>>() {
    @Override
    public void onSuccess(List<Photo> photos, Response response) {
        List<Photo> filteredPhotos = new ArrayList<Photo>();
        for(Photo photo: photos) {
            if(photo.isPNG()) {
                filteredList.add(photo);
            }
        }
    }
});

Maintenant, la variante RxJava n'est toujours pas plus petite, bien qu'avec les Lambdas, elle se rapprocherait de la variante Callback. De plus, si vous avez accès au flux JSON, il serait un peu bizarre de récupérer toutes les photos alors que vous n'affichez que les PNG. Il suffit d'ajuster le flux pour qu'il n'affiche que les PNG.

Première conclusion

Cela ne réduit pas votre base de code lorsque vous chargez un simple JSON que vous avez préparé pour être dans le bon format.

Maintenant, rendons les choses un peu plus intéressantes. Disons que vous ne voulez pas seulement récupérer la photo de l'utilisateur, mais que vous avez un clone d'Instagram, et que vous voulez récupérer 2 JSONs : 1. getUserDetails() 2. getUserPhotos()

Vous voulez charger ces deux JSON en parallèle, et lorsque les deux sont chargés, la page doit s'afficher. La variante callback va devenir un peu plus difficile : vous devez créer 2 callbacks, stocker les données dans l'activité, et si toutes les données sont chargées, afficher la page :

Rappel :

api.getUserDetails(userId, new Callback<UserDetails>() {
    @Override
    public void onSuccess(UserDetails details, Response response) {
        this.details = details;
        if(this.photos != null) {
            displayPage();
        }
    }
});

api.getUserPhotos(userId, new Callback<List<Photo>>() {
    @Override
    public void onSuccess(List<Photo> photos, Response response) {
        this.photos = photos;
        if(this.details != null) {
            displayPage();
        }
    }
});

RxJava :

private class Combined {
    UserDetails details;
    List<Photo> photos;
}

Observable.zip(api.getUserDetails(userId), api.getUserPhotos(userId), new Func2<UserDetails, List<Photo>, Combined>() {
            @Override
            public Combined call(UserDetails details, List<Photo> photos) {
                Combined r = new Combined();
                r.details = details;
                r.photos = photos;
                return r;
            }
        }).subscribe(new Action1<Combined>() {
            @Override
            public void call(Combined combined) {
            }
        });

Nous arrivons à quelque chose ! Le code de RxJava est maintenant aussi gros que l'option callback. Le code de RxJava est plus robuste ; Pensez à ce qui se passerait si nous avions besoin de charger un troisième JSON (comme les dernières vidéos) ? La RxJava n'aurait besoin que d'un ajustement minuscule, alors que la variante Callback doit être ajustée à plusieurs endroits (à chaque callback, nous devons vérifier si toutes les données sont récupérées).

Un autre exemple : nous voulons créer un champ de saisie automatique qui charge des données en utilisant Retrofit. Nous ne voulons pas faire un webcall à chaque fois qu'un EditText a un TextChangedEvent. Lorsque l'on tape rapidement, seul le dernier élément doit déclencher l'appel. Sur RxJava, nous pouvons utiliser l'opérateur debounce :

inputObservable.debounce(1, TimeUnit.SECONDS).subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                // use Retrofit to create autocompletedata
            }
        });

Je ne vais pas créer la variante Callback mais vous comprendrez que cela représente beaucoup plus de travail.

Conclusion : RxJava est exceptionnellement bon lorsque les données sont envoyées sous forme de flux. L'Observable Retrofit pousse tous les éléments du flux en même temps. Ce n'est pas particulièrement utile en soi par rapport au Callback. Mais quand il y a plusieurs éléments poussés sur le flux et à des moments différents, et que vous devez faire des choses liées au timing, RxJava rend le code beaucoup plus facile à maintenir.

68voto

Niels Points 742

L'Observable est déjà fait dans Retrofit, donc le code pourrait être le suivant :

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
         @Override
            public void call(Photo photo) {
                //save photo?
            }
     });

6 votes

Oui, mais la question est de savoir quand préférer l'un à l'autre.

8 votes

En quoi est-ce mieux qu'un simple rappel ?

17 votes

@MartynasJurkus Les observables peuvent être utiles si vous voulez enchaîner plusieurs fonctions. Par exemple, l'appelant veut obtenir une photo qui est recadrée aux dimensions 100x100. L'API peut renvoyer une photo de n'importe quelle taille, vous pouvez donc faire correspondre l'observable getUserPhoto à un autre observable ResizedPhotoObservable - l'appelant est seulement notifié lorsque le redimensionnement est effectué. Si vous n'avez pas besoin de l'utiliser, ne le forcez pas.

35voto

Niels Points 742

Dans le cas de getUserPhoto(), les avantages pour RxJava ne sont pas très importants. Mais prenons un autre exemple où vous obtiendrez toutes les photos d'un utilisateur, mais seulement si l'image est un PNG, et vous n'avez pas accès au JSON pour effectuer le filtrage sur le serveur.

api.getUserPhotos(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<List<Photo>, Observable<Photo>>() {
    @Override
    public Observable<Photo> call(List<Photo> photos) {
         return Observable.from(photos);
    }
})
.filter(new Func1<Photo, Boolean>() {
    @Override
    public Boolean call(Photo photo) {
         return photo.isPNG();
    }
})
.subscribe(
    new Action1<Photo>() {
    @Override
        public void call(Photo photo) {
            // on main thread; callback for each photo, add them to a list or something.
            list.add(photo)
        }
    }, 
    new Action1<Throwable>() {
    @Override
        public void call(Throwable throwable) {
            // on main thread; something went wrong
            System.out.println("Error! " + throwable);
        }
    }, 
    new Action0() {
        @Override
        public void call() {
            // on main thread; all photo's loaded, time to show the list or something.
        }
    });

Maintenant, le JSON renvoie une liste de photos. Nous allons les mettre en correspondance avec des éléments individuels. Ce faisant, nous pourrons utiliser la méthode de filtrage pour ignorer les photos qui ne sont pas au format PNG. Ensuite, nous nous abonnerons et obtiendrons un rappel pour chaque photo individuelle, un gestionnaire d'erreur et un rappel lorsque toutes les lignes auront été complétées.

TLDR Le point ici est que le callback ne vous renvoie qu'un callback pour les succès et les échecs ; l'Observable de RxJava vous permet de faire du map, reduce, filter et bien d'autres choses encore.

0 votes

Tout d'abord, subscribeOn et observeOn ne sont pas nécessaires avec le rétrofit. Il effectuera l'appel réseau de manière asynchrone et notifiera sur le même thread que l'appelant. Lorsque vous ajoutez RetroLambda pour obtenir des expressions lambda, cela devient beaucoup plus agréable.

0 votes

Mais je peux le faire (l'image du filtre est un PNG) dans le .subscribe() Pourquoi devrais-je utiliser le filtre ? et il n'y a aucun besoin dans flatmap

3 votes

3 réponses de @Niels. La première répond à la question. Aucun avantage pour la 2ème

28voto

Roger Garzon Nieto Points 1006

Avec rxjava, vous pouvez faire plus de choses avec moins de code.

Supposons que vous souhaitiez implémenter la recherche instantanée dans votre application. Avec les callbacks, vous devez vous soucier de désabonner la requête précédente et de vous abonner à la nouvelle, de gérer vous-même le changement d'orientation... Je pense que c'est beaucoup de code et trop verbeux.

Avec rxjava, c'est très simple.

public class PhotoModel{
  BehaviorSubject<Observable<Photo>> subject = BehaviorSubject.create(...);

  public void setUserId(String id){
   subject.onNext(Api.getUserPhoto(photoId));
  }

  public Observable<Photo> subscribeToPhoto(){
    return Observable.switchOnNext(subject);
  }
}

si vous souhaitez mettre en œuvre la recherche instantanée, il vous suffit d'écouter le TextChangeListener et d'appeler la fonction photoModel.setUserId(EditText.getText());

Dans la méthode onCreate du fragment ou de l'activité, vous vous abonnez à l'Observable qui renvoie photoModel.subscribeToPhoto(), il renvoie un Observable qui émet toujours les éléments émis par le dernier Observable(requête).

AndroidObservable.bindFragment(this, photoModel.subscribeToPhoto())
                 .subscribe(new Action1<Photo>(Photo photo){
      //Here you always receive the response of the latest query to the server.
                  });

De plus, si PhotoModel est un Singleton, par exemple, vous n'avez pas à vous soucier des changements d'orientation, car BehaviorSubject émet la dernière réponse du serveur, indépendamment du moment où vous vous abonnez.

Avec ces lignes de code, nous avons implémenté une recherche instantanée et géré les changements d'orientation. Pensez-vous que vous pouvez implémenter ceci avec des callbacks avec moins de code ? J'en doute.

0 votes

Ne pensez-vous pas que faire de la classe PhotoModel un Singleton est en soi une restriction ? Imaginez qu'il ne s'agit pas d'un profil d'utilisateur mais d'un ensemble de photos pour plusieurs utilisateurs, ne recevrons-nous pas uniquement la dernière photo d'utilisateur demandée ? De plus, demander des données à chaque changement d'orientation me semble un peu bizarre, qu'en pensez-vous ?

2voto

Dzmitry Lazerka Points 328

Nous suivons généralement la logique suivante :

  1. S'il s'agit d'un simple appel à réponse unique, alors Callback ou Future est préférable.
  2. S'il s'agit d'un appel avec de multiples réponses (flux), ou lorsqu'il y a une interaction complexe entre différents appels (voir l'article de @Niels réponse ), alors les Observables sont préférables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X