Quand faut-il utiliser RxJava Observable et quand un simple rappel sur Android?
Je travaille sur la mise en réseau pour mon application. J'ai donc décidé d'essayer Square Rénovation. Je vois qu'ils supportent simple Callback
@GET("/user/{id}/photo")
void getUserPhoto(@Path("id") int id, Callback<Photo> cb);
Et RxJava Observable
@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);
Les deux semblent assez similaires à première vue, mais quand il arrive à la mise en œuvre, cela devient intéressant...
Alors qu'avec une simple implémentation de rappel ressemblerait à ceci:
api.getUserPhoto(photoId, new Callback<Photo>() {
@Override
public void onSuccess() {
}
});
Ce qui est assez simple et direct. Et avec Observable
il devient rapidement verbeux et tout à fait 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 ça. Vous devez toujours faire quelque chose comme ceci:
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 manque quelque chose ici? Ou est-ce un mauvais cas à utiliser Observable
s?
Quand préférerait-on Observable
sur un simple rappel?
Mettre à jour
L'utilisation de retrofit est beaucoup plus simple que l'exemple ci-dessus comme @Niels l'a montré dans sa réponse ou dans L'exemple de Jake Wharton project U2020. Mais essentiellement, la question reste la même-quand faut-il utiliser one way ou le les autres?
7 réponses
Pour les choses de mise en réseau simples, les avantages de RxJava sur le rappel est très limité. L'exemple simple 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 de rappel. 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 Lambdas ce serait plus proche de la variante de rappel. De plus, si vous avez accès au flux JSON, il serait un peu bizarre de récupérer toutes les photos lorsque vous affichez uniquement les fichiers PNG. Il suffit d'ajuster le flux pour qu'il affiche uniquement les png.
Première conclusion
Cela ne réduit pas votre base de code lorsque vous chargez un JSON simple 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 pour récupérer l'userPhoto, mais vous avez un Instagram-clone, et vous voulez récupérer 2 JSONs: 1. getUserDetails() 2. getUserPhotos ()
Vous voulez charger ces deux JSONs en parallèle, et lorsque les deux sont chargés, la page doit être affichée. La variante de rappel deviendra un peu plus difficile: vous devez créer 2 rappels, stocker les données dans l'activité, et si toutes les données sont chargées, afficher le 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 part! Le code de RxJava est maintenant aussi grand que l'option de rappel. Le code RxJava est plus robuste; Pensez à ce qui se passerait si nous avions besoin d'un troisième JSON à charger (comme les dernières vidéos)? Le RxJava n'aurait besoin que d'un petit ajustement, tandis que la variante de rappel doit être ajustée à plusieurs endroits (sur chaque rappel, 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 semi-automatique, qui charge les données à L'aide de Retrofit. Nous ne voulons pas faire de webcall chaque fois Qu'un EditText a un TextChangedEvent. Lors de la saisie rapide, 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 de rappel mais vous comprendrez que c'est beaucoup plus de travail.
Conclusion: RxJava est exceptionnellement bon lorsque les données sont envoyées en tant que flux. Le Retrofit Observable pousse tous les éléments sur le flux en même temps. Ce n'est pas particulièrement utile en soi par rapport au rappel. 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 maintenable.
Les choses observables sont déjà faites en rénovation, donc le code pourrait être ceci:
api.getUserPhoto(photoId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Photo>() {
@Override
public void call(Photo photo) {
//save photo?
}
});
Dans le cas de getUserPhoto () les avantages pour RxJava ne sont pas grands. Mais prenons un autre exemple lorsque vous obtiendrez toutes les photos pour un utilisateur, mais seulement lorsque l'image est PNG, et que vous n'avez pas accès au JSON pour faire le filtrage côté 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 flatMap aux éléments individuels. Ce faisant, nous serons en mesure d'utiliser la méthode de filtre pour ignorer les photos qui ne sont pas PNG. Après cela, nous allons vous abonner et obtenir un rappel pour chaque photo individuelle, un errorHandler et un rappel lorsque toutes les lignes ont été terminées.
TLDR Point ici étant; le rappel ne vous renvoie qu'un rappel pour le succès et l'échec; le RxJava Observable vous permet de faire la carte, réduire, filtrer et bien d'autres choses.
, Avec rxjava vous pouvez faire plus de choses avec moins de code.
Supposons que vous souhaitez implémenter la recherche instantanée dans votre application. Avec les rappels, vous avez peur de vous désabonner de la demande précédente et de vous abonner à la nouvelle, gérez vous-même le changement d'orientation... Je pense que c'est beaucoup de code et trop verbeux.
Avec rxjava 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 implémenter une recherche instantanée, il vous suffit d'écouter TextChangeListener et d'appeler photoModel.setUserId(EditText.getText());
Dans onCreate méthode de Fragment ou d'activité vous vous abonnez à L'Observable qui renvoie photoModel.subscribeToPhoto (), il renvoie une Observable qui émet toujours les éléments émis par la dernière 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.
});
En outre, 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, quel que soit le moment où vous vous abonnez.
Avec ces lignes de code, nous avons implémenté une recherche instantanée et gérer les changements d'orientation. Pensez-vous que vous pouvez implémenter cela avec des rappels avec moins de code? J'en doute.
On dirait que vous réinventez la roue, ce que vous faites est déjà implémenté dans retrofit.
Pour un exemple, vous pouvez regarder RestAdapterTest de retrofit.java, où ils définir une interface avec Observables comme type de retour, et alors utiliser.
Par les échantillons et les conclusions dans d'autres réponses, je pense qu'il n'y a pas de grande différence pour les tâches simples en une ou deux étapes. Cependant, le rappel est simple et direct. RxJava est plus compliqué et trop grand pour une tâche simple. Il y a la troisième solution par: AbacusUtil . Permettez-moi d'implémenter les cas d'utilisation ci-dessus avec les trois solutions: Callback, RxJava, CompletableFuture (AbacusUtil) avec Retrolambda :
Récupérer la photo du réseau et enregistrer / afficher sur dispositif:
// By Callback
api.getUserPhoto(userId, new Callback<Photo>() {
@Override
public void onResponse(Call<Photo> call, Response<Photo> response) {
save(response.body()); // or update view on UI thread.
}
@Override
public void onFailure(Call<Photo> call, Throwable t) {
// show error message on UI or do something else.
}
});
// By RxJava
api.getUserPhoto2(userId) //
.observeOn(AndroidSchedulers.mainThread())
.subscribe(photo -> {
save(photo); // or update view on UI thread.
}, error -> {
// show error message on UI or do something else.
});
// By Thread pool executor and CompletableFuture.
TPExecutor.execute(() -> api.getUserPhoto(userId))
.thenRunOnUI((photo, error) -> {
if (error != null) {
// show error message on UI or do something else.
} else {
save(photo); // or update view on UI thread.
}
});
Charger les détails de l'utilisateur et la photo en parallèle
// By Callback
// ignored because it's little complicated
// By RxJava
Observable.zip(api.getUserDetails2(userId), api.getUserPhoto2(userId), (details, photo) -> Pair.of(details, photo))
.subscribe(p -> {
// Do your task.
});
// By Thread pool executor and CompletableFuture.
TPExecutor.execute(() -> api.getUserDetails(userId))
.runOnUIAfterBoth(TPExecutor.execute(() -> api.getUserPhoto(userId)), p -> {
// Do your task
});
Nous allons généralement avec la logique suivante:
- S'il s'agit d'un simple appel à réponse unique, alors Callback ou Future est meilleur.
- s'il s'agit d'un appel avec plusieurs réponses (flux), ou lorsqu'il y a une interaction complexe entre différents appels (voir la réponse de @Niels), alors les Observables sont meilleurs.