example - ¿Cuándo utilizas map vs flatMap en RxJava?
rxjava flatmap (9)
¿Cuándo utilizas map vs flatMap en RxJava?
Digamos, por ejemplo, que queremos asignar archivos que contienen JSON a cadenas que contienen el JSON--
Usando el mapa, tenemos que lidiar con la excepción de alguna manera. ¿Pero cómo?:
Observable.from(jsonFile).map(new Func1<File, String>() {
@Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
// So Exception. What to do ?
}
return null; // Not good :(
}
});
Usar flatMap es mucho más detallado, pero podemos reenviar el problema por la cadena de Observables y manejar el error si elegimos otro sitio e incluso lo intentamos de nuevo:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
@Override public Observable<String> call(final File file) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override public void call(Subscriber<? super String> subscriber) {
try {
String json = new Gson().toJson(new FileReader(file), Object.class);
subscriber.onNext(json);
subscriber.onCompleted();
} catch (FileNotFoundException e) {
subscriber.onError(e);
}
}
});
}
});
Me gusta la simplicidad del mapa, pero el manejo de errores de flatmap (no la verbosidad). No he visto ninguna de las mejores prácticas sobre este tema y estoy curioso sobre cómo se está utilizando en la práctica.
Aquí hay una regla simple que utilizo para ayudarme a decidir cuándo usar flatMap()
sobre map()
en Rx''s Observable
.
Una vez que llega a la conclusión de que va a emplear una transformación de map
, debe escribir su código de transformación para devolver algún objeto, ¿verdad?
Si lo que estás devolviendo como resultado final de tu transformación es:
un objeto no observable entonces usarías solo
map()
. Ymap()
envuelve ese objeto en un Observable y lo emite.un Objeto
Observable
, entonces usaríaflatMap()
. YflatMap()
desenvuelve el Observable, elige el objeto devuelto, lo envuelve con su propio Observable y lo emite.
Digamos, por ejemplo, que tenemos un método titleCase (String inputParam) que devuelve el objeto Titled Cased String del param de entrada. El tipo de devolución de este método puede ser String
u Observable<String>
.
Si el tipo de
titleCase(..)
detitleCase(..)
fuera simpleString
, entonces usaríamap(s -> titleCase(s))
Si el tipo de retorno de
titleCase(..)
fueraObservable<String>
, entonces usaríaflatMap(s -> titleCase(s))
Espero que eso aclare.
En algunos casos, puede terminar teniendo una cadena de observables, en donde su observable devolvería otra observable. El tipo de "mapa plano" desenvuelve el segundo observable que está oculto en el primero y le permite acceder directamente a los datos que el segundo observable escupió al suscribirse.
En ese escenario usa mapa, no necesitas un nuevo Observable para él.
debe usar Exceptions.propagate, que es un contenedor para que pueda enviar esas excepciones marcadas al mecanismo rx
Observable<String> obs = Observable.from(jsonFile).map(new Func1<File, String>() {
@Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
throw Exceptions.propagate(t); /will propagate it as error
}
}
});
Luego debe manejar este error en el suscriptor
obs.subscribe(new Subscriber<String>() {
@Override
public void onNext(String s) { //valid result }
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { //e might be the FileNotFoundException you got }
};);
Hay una excelente publicación para él: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/
En tu caso, creo que necesitas un mapa, ya que solo hay 1 entrada y 1 salida.
la función suministrada por el mapa simplemente acepta un ítem y devuelve un ítem que se emitirá más (solo una vez) hacia abajo.
La función proporcionada por flatMap acepta un elemento y luego devuelve un "Observable", lo que significa que cada elemento del nuevo "Observable" se emitirá por separado más abajo.
Puede ser que el código te aclare las cosas.
//START DIFFERENCE BETWEEN MAP AND FLATMAP
Observable.just("item1")
.map( str -> {
System.out.println("inside the map " + str);
return str;
})
.subscribe(System.out::println);
Observable.just("item2")
.flatMap( str -> {
System.out.println("inside the flatMap " + str);
return Observable.just(str + "+", str + "++" , str + "+++");
})
.subscribe(System.out::println);
//END DIFFERENCE BETWEEN MAP AND FLATMAP
Salida:
inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++
FlatMap se comporta de forma muy parecida al mapa, la diferencia es que la función que aplica devuelve un valor observable por sí mismo, por lo que se adapta perfectamente a las operaciones asincrónicas.
En el sentido práctico, la función Mapa se aplica solo hace una transformación sobre la respuesta encadenada (no devuelve un Observable); mientras la función FlatMap aplica, devuelve un Observable<T>
, por eso se recomienda FlatMap si planea hacer una llamada asincrónica dentro del método.
Resumen:
- Mapa devuelve un objeto de tipo T
- FlatMap devuelve un Observable.
Un ejemplo claro se puede ver aquí: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .
Couchbase Java 2.X Client utiliza Rx para proporcionar llamadas asíncronas de manera conveniente. Como usa Rx, tiene el mapa de métodos y FlatMap, la explicación en su documentación puede ser útil para comprender el concepto general.
Para manejar los errores, anule onError en su suscriptor.
Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) { System.out.println(s); }
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { }
};
Puede ser útil mirar este documento: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/
Puede encontrar una buena fuente sobre cómo administrar errores con RX en: https://gist.github.com/daschl/db9fcc9d2b932115b679
La pregunta es ¿ Cuándo usas map vs flatMap en RxJava? . Y creo que una demostración simple es más específica.
Cuando desee convertir un elemento emitido en otro tipo, en su caso convertir el archivo a String, el mapa y el mapa plano pueden funcionar. Pero prefiero el operador de mapas porque es más claro.
Sin embargo, en algún lugar, flatMap
puede hacer un trabajo mágico, pero el map
no puede. Por ejemplo, quiero obtener información de un usuario, pero primero tengo que obtener su identificación cuando el usuario inicie sesión. Obviamente necesito dos solicitudes y están en orden.
Vamos a empezar.
Observable<LoginResponse> login(String email, String password);
Observable<UserInfo> fetchUserInfo(String userId);
Aquí hay dos métodos, uno para la Response
de inicio de sesión devuelto y otro para obtener información del usuario.
login(email, password)
.flatMap(response ->
fetchUserInfo(response.id))
.subscribe(userInfo -> {
// get user info and you update ui now
});
Como ve, en la función que se aplica flatMap, al principio obtengo el ID de usuario de Response
luego obtengo la información del usuario. Cuando dos solicitudes finalizan, podemos hacer nuestro trabajo, como actualizar la interfaz de usuario o guardar datos en la base de datos.
Sin embargo, si usa el map
no puede escribir un código tan agradable. En una palabra, flatMap
puede ayudarnos a serializar las solicitudes.
Lo que pienso es que usas flatMap
cuando la función que querías poner dentro de map()
devuelve un Observable
. En ese caso, podría intentar usar map()
pero no sería práctico. Déjame intentar explicar por qué.
Si en tal caso decidieras quedarte con el map
, obtendrías un Observable<Observable<Something>>
. Por ejemplo, en su caso, si usamos una biblioteca RxGson imaginaria, que devolvió un Observable<String>
de su método toJson()
(en lugar de simplemente devolver una String
) se vería así:
Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
@Override public Observable<String>> call(File file) {
return new RxGson().toJson(new FileReader(file), Object.class);
}
}); // you get Observable<Observable<String>> here
En este punto, sería muy difícil subscribe()
a un observable. En su interior obtendría un Observable<String>
al que tendría que volver a subscribe()
para obtener el valor. Lo cual no es práctico ni agradable de mirar.
Entonces, para que sea útil, una idea es "aplanar" este observable de observables (puede comenzar a ver de dónde viene el nombre _flat_Map). RxJava proporciona algunas formas de aplanar los observables y, para simplificar, permite suponer que la merge es lo que queremos. Merge básicamente toma un montón de observables y emite cada vez que alguno de ellos emite. (Mucha gente diría que el switch sería un mejor incumplimiento, pero si estás emitiendo solo un valor, no importa de todos modos).
Entonces, al modificar nuestro fragmento anterior obtendríamos:
Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
@Override public Observable<String>> call(File file) {
return new RxGson().toJson(new FileReader(file), Object.class);
}
}).merge(); // you get Observable<String> here
Esto es mucho más útil, porque suscribiéndote a eso (o mapeo, o filtrado, o ...) simplemente obtienes el valor de String
. (Además, tenga en cuenta que esa variante de merge()
no existe en RxJava, pero si comprende la idea de combinar, espero que también comprenda cómo funcionaría eso).
Así que, básicamente, debido a que tal merge()
probablemente sea útil cuando logre que un map()
devuelva un carácter observable y así no tenga que escribir esto una y otra vez, se creó flatMap()
como una forma abreviada. Aplica la función de mapeo tal como lo haría un map()
normal map()
, pero más tarde en lugar de emitir los valores devueltos, también los "aplana" (o fusiona).
Ese es el caso de uso general. Es más útil en una base de código que usa Rx en todo lugar y tienes muchos métodos que devuelven valores observables, que deseas encadenar con otros métodos que devuelven valores observables.
En su caso de uso, también resulta útil, porque map()
solo puede transformar un valor emitido en onNext()
en otro valor emitido en onNext()
. Pero no puede transformarlo en valores múltiples, ningún valor en absoluto o un error. Y como akarnokd escribió en su respuesta (y fíjate que es mucho más inteligente que yo, probablemente en general, pero al menos en lo que respecta a RxJava) no debes arrojar excepciones desde tu map()
. Entonces puedes usar flatMap()
y
return Observable.just(value);
cuando todo va bien, pero
return Observable.error(exception);
cuando algo falla
Consulte su respuesta para obtener un fragmento completo: https://.com/a/30330772/1402641
Solo quería agregar eso con flatMap
, realmente no necesita usar su propio Observable personalizado dentro de la función y puede confiar en los métodos / operadores estándar de fábrica:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
@Override public Observable<String> call(final File file) {
try {
String json = new Gson().toJson(new FileReader(file), Object.class);
return Observable.just(json);
} catch (FileNotFoundException ex) {
return Observable.<String>error(ex);
}
}
});
Por lo general, debe evitar las excepciones de lanzamiento (Runtime-) de los métodos y las devoluciones de llamada deXXX, si es posible, aunque hayamos puesto todas las salvaguardas posibles en RxJava.
map
transforma un evento a otro. flatMap
transforma un evento en cero o más eventos. (esto está tomado de IntroToRx )
Como quiera transformar su json a un objeto, usar el mapa debería ser suficiente.
Tratar con la excepción FileNotFoundException es otro problema (el uso de mapa o mapa plano no resolvería este problema).
Para resolver su problema de Excepción, simplemente ejecútelo con una excepción No verificada: RX llamará al controlador onError por usted.
Observable.from(jsonFile).map(new Func1<File, String>() {
@Override public String call(File file) {
try {
return new Gson().toJson(new FileReader(file), Object.class);
} catch (FileNotFoundException e) {
// this exception is a part of rx-java
throw OnErrorThrowable.addValueAsLastCause(e, file);
}
}
});
la misma versión exacta con flatmap:
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
@Override public Observable<String> call(File file) {
try {
return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
} catch (FileNotFoundException e) {
// this static method is a part of rx-java. It will return an exception which is associated to the value.
throw OnErrorThrowable.addValueAsLastCause(e, file);
// alternatively, you can return Obersable.empty(); instead of throwing exception
}
}
});
También puede devolver, en la versión de FlatMap, un nuevo Observable que es solo un error.
Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
@Override public Observable<String> call(File file) {
try {
return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
} catch (FileNotFoundException e) {
return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
}
}
});