web-dev-qa-db-fra.com

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 Retrofit . Je vois qu'ils supportent les simples Callback

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

et RxJava's Observable

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

Les deux ressemblent beaucoup à première vue, mais quand on arrive à la mise en œuvre, ça devient intéressant ...

Alors qu'avec un rappel simple, l'implémentation ressemblerait à ceci:

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

ce qui est assez simple et direct. Et avec Observable cela devient rapidement compliqué et prolixe.

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 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 j'ai râté quelque chose? Ou est-ce un mauvais cas pour utiliser Observables? Quand préférerions-nous préférer Observable à un simple rappel?

Mise à jour

Utiliser retrofit est beaucoup plus simple que l'exemple ci-dessus, comme l'a montré @Niels dans sa réponse ou dans l'exemple de projet de Jake Wharton 202 . Mais la question reste essentiellement la même: quand utiliser l’un ou l’autre?

250
Martynas Jurkus

Pour les tâches de réseau simples, les avantages de RxJava par rapport au rappel sont très limités. 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 guère meilleure que la variante Callback. Pour l'instant, ignorons le traitement 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 que, avec Lambdas, elle devienne plus proche de la variante Callback. De plus, si vous avez accès au flux JSON, il serait assez étrange de récupérer toutes les photos lorsque vous n’affichez que les fichiers PNG. Il suffit d’ajuster l’alimentation pour afficher uniquement les PNG.

Première conclusion

Cela ne rend pas votre base de code plus petite lorsque vous chargez un simple JSON que vous avez préparé pour être au bon format.

Maintenant, rendons les choses un peu plus intéressantes. Supposons que vous souhaitiez non seulement récupérer userPhoto, mais également un clone Instagram et que vous souhaitiez récupérer 2 fichiers JSON: 1. getUserDetails () 2. getUserPhotos ()

Vous souhaitez charger ces deux JSON 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 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 allons quelque part! Le code de RxJava est maintenant aussi gros 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 ajustement minime, tandis que la variante de rappel devra être ajustée à plusieurs endroits (à 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 autocomplete, qui charge les données en utilisant Retrofit. Nous ne voulons pas faire un appel Web 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 anti-rebond:

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

Je ne créerai pas 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 sous forme de flux. L'observable de rattrapage pousse tous les éléments du 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 effectuer des tâches liées à la synchronisation, RxJava rend le code beaucoup plus facile à gérer.

337
Niels

Le processus Observable est déjà effectué dans Retrofit, le code pourrait donc être le suivant:

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

Dans le cas de getUserPhoto (), les avantages pour RxJava ne sont pas énormes. Mais prenons un autre exemple lorsque vous obtenez toutes les photos d'un utilisateur, mais uniquement lorsque l'image est au format PNG et que vous n'avez pas accès au JSON pour effectuer le filtrage au niveau du 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 retourne une liste de photos. Nous allons les mapper à des éléments individuels. Ce faisant, nous pourrons utiliser la méthode du filtre pour ignorer les photos qui ne sont pas au format PNG. Après cela, nous nous abonnerons et aurons 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 étant; le rappel ne vous renvoie qu'un rappel en cas de succès ou d'échec; le RxJava Observable vous permet de faire la carte, réduire, filtrer et beaucoup plus de choses.

35
Niels

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 rappels, vous craignez 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 détaillé.

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 implémenter la recherche instantanée, il vous suffit d'écouter TextChangeListener et d'appeler à photoModel.setUserId(EditText.getText());

Dans la méthode onCreate de fragment ou d'activité, vous vous abonnez à l'Observable qui renvoie photoModel.subscribeToPhoto (), il renvoie un Observable qui émet toujours les éléments émis par la dernière Observable (demande).

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 même, 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 de votre abonnement.

Avec ces lignes de code, nous avons mis en place une recherche instantanée et gérons les changements d’orientation. Pensez-vous que vous pouvez implémenter cela avec des rappels avec moins de code? J'en doute.

27
Roger Garzon Nieto

Nous allons généralement avec la logique suivante:

  1. S'il s'agit d'un simple appel à réponse unique, le rappel ou l'avenir est préférable.
  2. S'il s'agit d'un appel à réponses multiples (flux), ou lorsqu'il existe une interaction complexe entre différents appels (voir @Niels ' answer ), les observables sont meilleurs.
2
Dzmitry Lazerka

D'après les exemples et les conclusions des 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 gros pour une tâche simple. Il y a la troisième solution par: AbacusUtil . Permettez-moi de mettre en œuvre les cas d'utilisation ci-dessus avec les trois solutions: Callback, RxJava, CompletableFuture (AbacusUtil) avec Retrolambda :

Récupérer une photo du réseau et enregistrer/afficher sur l'appareil:

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

Charge 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
});
1
user_3380739

On dirait que vous réinventez la roue, ce que vous faites est déjà mis en œuvre dans la modernisation.

Pour un exemple, vous pouvez regarder retrofit RestAdapterTest.Java , où ils définissent une interface avec Observable comme type de retour, puis tilisez-le .

0
Samuel Gruetter

Personnellement, je préfère utiliser Rx pour obtenir des réponses API dans les cas où je dois filtrer, mapper ou quelque chose comme ça sur les données, ou dans les cas où je dois effectuer un autre appel API en fonction des réponses aux appels précédents

0
Reza