rxjava mvc android retrofit rx-java

android - mvc - rxjava



¿Cuándo debería uno usar RxJava Observable y cuándo un simple Callback en Android? (7)

Estoy trabajando en redes para mi aplicación. Así que decidí probar Square''s Retrofit . Veo que apoyan la Callback simple

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

y Observable de RxJava

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

Ambos parecen bastante similares a primera vista, pero cuando llega a la implementación se vuelve interesante ...

Mientras que con la simple implementación de devolución de llamada se vería similar a esto:

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

que es bastante simple y directo. Y con Observable se vuelve rápidamente verboso y bastante complicado.

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()); }

Y eso no es todo. Todavía tienes que hacer algo como esto:

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? } });

¿Me estoy perdiendo de algo? ¿O es este un caso equivocado para usar Observable s? ¿Cuándo preferiría / debería uno Observable a simple devolución de llamada?

Actualizar

El uso de retrofit es mucho más simple que el ejemplo anterior, como @Niels mostró en su respuesta o en el proyecto de ejemplo U2020 Jake Wharton. Pero, en esencia, la pregunta sigue siendo la misma: ¿cuándo se debe usar una u otra forma?


Con rxjava puedes hacer más cosas con menos código.

Supongamos que desea implementar la búsqueda instantánea en su aplicación. Con las devoluciones de llamada le ha preocupado anular la suscripción de la solicitud anterior y suscribirse a la nueva, manejar la orientación cambiar usted mismo ... Creo que es una gran cantidad de código y demasiado detallado.

Con rxjava es muy 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 desea implementar la búsqueda instantánea, solo tiene que escuchar TextChangeListener y llamar a photoModel.setUserId(EditText.getText());

En el método de Fragmento o actividad de onCreate, usted se suscribe al Observable que devuelve photoModel.subscribeToPhoto (), devuelve un Observable que siempre emite los elementos emitidos por la última Observable (solicitud).

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

Además, si PhotoModel es un Singleton, por ejemplo, no necesita preocuparse por los cambios de orientación, ya que BehaviorSubject emite la última respuesta del servidor, independientemente de cuándo se suscriba.

Con estas líneas de código hemos implementado una búsqueda instantánea y manejar cambios de orientación. ¿Crees que puedes implementar esto con devoluciones de llamada con menos código? Lo dudo.


El material observable ya está hecho en Retrofit, por lo que el código podría ser esto:

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


En el caso de getUserPhoto () las ventajas para RxJava no son grandes. Pero tomemos otro ejemplo cuando obtendrá todas las fotos para un usuario, pero solo cuando la imagen sea PNG, y no tenga acceso al JSON para realizar el filtrado en el servidor.

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

Ahora el JSON devuelve una lista de fotos. Los planearemos a los artículos individuales. Al hacerlo, podremos usar el método de filtro para ignorar las fotos que no son PNG. Después de eso, nos suscribiremos y obtendremos una devolución de llamada para cada foto individual, un errorHandler y una devolución de llamada cuando todas las filas se hayan completado.

TLDR Punto aquí siendo; la devolución de llamada solo le devuelve una devolución de llamada para el éxito y el fracaso; RxJava Observable te permite hacer mapas, reducir, filtrar y muchas más cosas.


Para cosas de redes simples, las ventajas de RxJava sobre Callback son muy limitadas. El ejemplo 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 } });

Llamar de vuelta:

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

La variante de RxJava no es mucho mejor que la variante de devolución de llamada. Por ahora, ignoremos el manejo de errores. Tomemos una lista de fotos:

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

Llamar de vuelta:

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

Ahora, la variante de RxJava todavía no es más pequeña, aunque con Lambdas se acercaría más a la variante de Callback. Además, si tienes acceso a la fuente JSON, sería un poco raro recuperar todas las fotos cuando solo estás mostrando los PNG. Solo ajuste la alimentación para que solo muestre PNGs.

Primera conclusión

No hace que su base de código sea más pequeña cuando está cargando un JSON simple que preparó para estar en el formato correcto.

Ahora, hagamos las cosas un poco más interesantes. Digamos que no solo desea recuperar la foto de usuario, sino que también tiene un clon de Instagram y desea recuperar 2 JSON: 1. getUserDetails () 2. getUserPhotos ()

Desea cargar estos dos JSON en paralelo, y cuando ambos se carguen, la página debería mostrarse. La variante de devolución de llamada será un poco más difícil: debe crear 2 devoluciones de llamada, almacenar los datos en la actividad y, si todos los datos están cargados, mostrar la página:

Llamar de vuelta:

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) { } });

¡Estamos llegando a alguna parte! El código de RxJava es ahora tan grande como la opción de devolución de llamada. El código RxJava es más robusto; Piense en lo que sucedería si necesitaríamos cargar un tercer JSON (como los últimos Videos). El RxJava solo necesitaría un pequeño ajuste, mientras que la variante de devolución de llamada debe ajustarse en múltiples lugares (en cada devolución de llamada debemos verificar si se recuperan todos los datos).

Otro ejemplo; queremos crear un campo de autocompletado, que carga los datos utilizando Retrofit. No queremos hacer una llamada web cada vez que un EditText tiene un TextChangedEvent. Al escribir rápido, solo el último elemento debe activar la llamada. En RxJava podemos usar el operador de rebote:

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

No crearé la variante de devolución de llamada, pero comprenderá que esto es mucho más trabajo.

Conclusión: RxJava es excepcionalmente bueno cuando los datos se envían como un flujo. El observable de actualización empuja todos los elementos en el flujo al mismo tiempo. Esto no es particularmente útil en sí mismo en comparación con Callback. Pero cuando se insertan varios elementos en el flujo y en diferentes momentos, y necesitas hacer cosas relacionadas con el tiempo, RxJava hace que el código sea mucho más fácil de mantener.



Por las muestras y conclusiones en otras respuestas, creo que no hay una gran diferencia para las tareas simples de uno o dos pasos. Sin embargo, Callback es simple y directo. RxJava es más complicado y demasiado grande para una tarea simple. Existe la tercera solución por: AbacusUtil . Permítame implementar los casos de uso anteriores con las tres soluciones: Devolución de llamada, RxJava, CompletableFuture (AbacusUtil) con Retrolambda :

Recupere la foto de la red y guárdela en el dispositivo:

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

Cargar datos del usuario y foto en paralelo.

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


Usualmente vamos con la siguiente lógica:

  1. Si es una llamada simple de una respuesta, entonces Callback o Future es mejor.
  2. Si se trata de una llamada con respuestas múltiples (flujo), o cuando hay una interacción compleja entre diferentes llamadas (ver la answer @Niels), entonces los Observables son mejores.