I have a Observable that emits a list of Location objects. These Location objects have a method getUser() which returns a User (really!?). What I'm trying to do is return a new Observable that emits a list of User objects given this list of Location objects. Here's my code:
Locations Observable
private Observable<List<QBLocation>> retrieveLocations(){
QBLocationRequestBuilder getLocationsBuilder = new QBLocationRequestBuilder();
getLocationsBuilder.setPerPage(100);
getLocationsBuilder.setLastOnly();
getLocationsBuilder.setCurrentPosition(mLastLocation.getLatitude(), mLastLocation.getLongitude());
getLocationsBuilder.setSort(SortField.CREATED_AT);
getLocationsBuilder.setSort(SortField.DISTANCE, SortOrder.DESCENDING);
return Observable.create(subscriber -> {
Bundle mBundle = new Bundle();
try {
ArrayList<QBLocation> qbLocations = QBLocations.getLocations(getLocationsBuilder, mBundle);
subscriber.onNext(qbLocations);
subscriber.onCompleted();
} catch (QBResponseException e) {
subscriber.onError(e);
}
});
}
User Observable
#Override
public Observable<List<QBUser>> retrieveUsers() {
return retrieveLocations()
.flatMap(qbLocations -> Observable.from(qbLocations))
.flatMap(qbLocation -> qbLocation.getUser())
//How do I return an observable list with these users?
}
You can use toList() operator to collect you items back into a list:
#Override
public Observable<List<QBUser>> retrieveUsers() {
return retrieveLocations()
.flatMap(qbLocations -> Observable.from(qbLocations))
.flatMap(qbLocation -> qbLocation.getUser())
.toList();
}
Your code could be simplified:
#Override
public Observable<List<QBUser>> retrieveUsers() {
return retrieveLocations()
.flatMapIterable(qbLocations -> qbLocations)
.map(qbLocation::getUser)
.toList();
}
Also, I'd rewrite your retrieveLocations as follows:
return Observable.defer(() -> Observable.just(QBLocations.getLocations(getLocationsBuilder, mBundle));
Which with some static imports and exception handling becomes:
return defer(() -> {
try {
return just(getLocations(getLocationsBuilder, mBundle));
} catch(Exeption e){
return Observable.error(e);
}
});
Abstracting the error handling will make it even easier to read:
#FunctionalInterface
public interface <T,R,E> ThrowningFunction { R apply(T t) throws E; }
#FunctionalInterface
public interface <R,E> ThrowningSupplier { R get() throws E; }
public static <T,R> Func1<T,Observable<R>> protect(ThrowningFunction<T,R,?> func) {
return t -> {
try {
return Observable.just(func.apply(t));
} catch(Exception e) {
return Observable.error(e);
}
}
}
public static <R> Func0<Observable<R>> protect(ThrowningSupplier<R,?> func) {
return () -> {
try {
return Observable.just(func.get());
} catch(Exception e) {
return Observable.error(e);
}
}
}
This makes your actual getLocations return like this:
return defer(() -> protect(() -> getLocations(getLocationsBuilder, mBundle)));
Whops, this has meandered out of control...
Related
I want to retrieve data of different types from a database and return to the user within an HTTP result from a Spring Boot service. Because the database retrieval takes a significant amount of time for each, I am making these DB calls asynchronously with CompletableFuture. The pattern I have works and saves time compared to doing this synchronously, but I feel that it can and should be laid out in a cleaner fashion.
I edited the code to change the types to 'PartA', 'PartB', 'PartC', but this is otherwise how it appears. Currently, the service accepts the lists of different types (PartA, PartB, PartC), creates Completable future types of each list calling its own CompletableFuture method that calls the DB, builds a generic list of CompleteableFutures with each type, "gets" the generic list, then adds all the contents of each Future list to the list passed into the service.
This is how the Service methods are coded:
Service.java:
public void metadata(final List<PartA> partAs,final List<PartB> partBs,final List<PartC> partCs,
String prefix,String base,String suffix) throws Exception {
try {
CompletableFuture<List<PartA>> futurePartAs = partACompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartA here");
return list;
});
CompletableFuture<List<PartB>> futurePartBs = partBCompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartBs here");
return list;
});
CompletableFuture<List<PartC>> futurePartCs = partCCompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartCs here");
return list;
});
CompletableFuture<?> combinedFuture = CompletableFuture.allOf(CompletableFuture.allOf(futurePartAs, futurePartBs, futurePartCs));
combinedFuture.get();
partAs.addAll(futurePartAs.get());
partBs.addAll(futurePartBs.get());
partCs.addAll(futurePartCs.get());
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
}
#Async("asyncExecutor")
public CompletableFuture<List<PartA>> partACompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start PartA");
return getPartAs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
#Async("asyncExecutor")
public CompletableFuture<List<PartB>> partBCompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start B");
return getPartBs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
#Async("asyncExecutor")
public CompletableFuture<List<PartC>> partCCompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start PartC");
return getPartCs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
In case you wish to view the Controller and Response type:
Controller.java
#GetMapping(value="/parts/metadata",produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<MetadataResponse> metadata (#ApiParam(name="prefix",value = "Prefix value for a part",required = false)
#RequestParam(required=false) String prefix,
#ApiParam(name="base",value = "Base value for a part",required= true)
#RequestParam String base,
#ApiParam(name="suffix",value = "Suffix value for a part",required=false)
#RequestParam(required=false) #NotBlank String suffix ) throws Exception {
final List<PartA> partAs = new ArrayList<>();
final List<PartB> partBs = new ArrayList<>();
final List<PartC> partCs = new ArrayList<>();
service.metadata(partAs,partBs,partCs,prefix,base,suffix);
MetadataResponse.MetadataResponseResult res = MetadataResponse.MetadataResponseResult.builder()
.partAs(partAs)
.partBs(partBs)
.partCs(partCs)
.build();
return ResponseEntity.ok(MetadataResponse.result(res, MetadataResponse.class));
}
MetadataResponse.java
#ApiModel(value = "MetadataResponse", parent = BaseBodyResponse.class, description = "Part A, B, C")
public class MetadataResponse extends BaseBodyResponse<MetadataResponse.MetadataResponseResult> {
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#ApiModel(value = "MetadataResponseResult", description = "This Model holds Part As, Bs, Cs")
public static class MetadataResponseResult {
List<PartA> partAs;
List<PartB> partBs;
List<PartC> partCs;
}
}
I don't understand exactly why you need to pass all these lists as parameters in this case: public void metadata(final List<PartA> partAs,final List<PartB> partBs,final List<PartC> partCs, String prefix,String base,String suffix) throws Exception You could modify this method to return the MetadataResponseResult class you already have and use the lists from the ComparableFutures directly
I would remove the thenApply methods since you just log a statement and you don't actually change the results.
Instead of having the three methods (partACompletableFuture, partABCompletableFuture, partCCompletableFuture) you could have one method that receives a Supplier as a parameter.
#Async("asyncExecutor")
public <T> CompletableFuture<T> partCompletableFuture(Supplier<T> supplier) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start Part");
return supplier.get();
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
Aftewards you can use it as so:
CompletableFuture<List<PartA>> futurePartAs = partCompletableFuture(() ->
getPartAs(prefix,base,suffix));
It should much cleaner. Hope this helped!
How can I rewrite this via a Java Lambda?
This question is different from "Lambda Expression and generic method" issue. The issue is because of throwing an Exception.
public Observable<String> getUserInfo() {
return Observable.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
try {
emitter.onNext( getPlayerInfo());
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
When I rewrite this via a normal Lambda, then I get this error:
Incompatible types: Required: Observable <java.lang.String>. Found:
Observable <java.lang.Object>.
This is what I tried:
public Observable<String> getUserInfo() {
return Observable.create( emitter -> {
try {
emitter.onNext(getPlayerInfo();
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
The inference engine of the compiler cannot determine the type parameter of Observable, so you need to explicitly specify it:
return Observable.<String>create( emitter -> {
Type inference fails specifically because of the trailing calls to subscribeOn and observeOn. Another solution is introducing an intermediate method or variable, for example:
private Observable<String> getUserInfo() {
Observable<String> userInfo = Observable.create( emitter -> {
try {
emitter.onNext(getPlayerInfo();
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
});
return userInfo
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
I have two sources of Singles, which I combine into one Single of Pair.
Let's say we have two methods for these sources:
private Single<String> single1() {}
private Single<String> single2() {}
Now I combine them into Single<Pair<String, String>
private Single<Pair<String, String> combineSingles() {
Single.zip(single1(), single2(), BiFunction<String, String, Pair<String, String>>) (t1, t2) -> new Pair(t1, t2))
}
When one of the methods (single1 or single2) returns Single.error(), I get UndeliverableException cause error isn't handled in zip operator. I want instead to return Single.error() from combineSingles() method, how can I achieve that?
try this
private Single<Pair<String, String> combineSingles() {
try {
Single.zip(single1(), single2(), BiFunction<String, String, Pair<String, String>>) (t1, t2) -> new Pair(t1, t2))
} catch (Exception e) {
Single.error()
}
}
by the way i was first to suggest the try catch method :)
onErrorResumeNext(...) is your friend, which is the try/catch equivalent for RxJava.
Here is the complete example
public final class RxTest {
private static Single<String> single1() {
return Single.just("1");
}
private static Single<String> single2() {
return Single.error(new RuntimeException("single2"));
}
private static Single<Pair<String, String>> combineSingles() {
return Single.zip(single1(), single2(), Pair::new)
.onErrorResumeNext(Single.error(new RuntimeException("zip")));
}
public static void main(String[] args) {
combineSingles()
.test()
.assertError(RuntimeException.class)
.assertError(throwable -> "zip".equals(throwable.getMessage()));
}
}
How about the usual try/catch logic?
Single<String> single1;
try {
single1 = single1();
} catch (RuntimeException e) {
return Single.error();
}
Single<String> single2;
try {
single2 = single2();
} catch (RuntimeException e) {
return Single.error();
}
return Single.zip(single1, single2, BiFunction<String, String, Pair<String, String>>) (t1, t2) -> new Pair(t1, t2))
Or if you want to replace the faulty value with Single.error(), then
Single<String> single1;
try {
single1 = single1();
} catch (RuntimeException e) {
single1 = Single.error();
}
And so on.
I'm a newbie in RxJava and I have some difficulties while constructing Observable.
My tasks are:
Query to server getExpPointsIdArrayByHouse
On server response. We get an object (RpcDeviceInfoResponse) that contain a list of integers
For each of int value a separate query to server is needed to be executed. Result of the each query is an object "ExpPoint"
Final result is a list of expPoints
What I've already done:
Observable
.defer(new Func0() {
#Override
public Object call() {
try {
return Observable.just(apiHttpClient.getExpPointsIdArrayByHouse(houseId));
} catch (IOException e) {
e.printStackTrace();
return Observable.error(e);
} catch (RightsException e) {
e.printStackTrace();
return Observable.error(e);
}
}
})
.flatMap(new Func1<RpcDeviceInfoResponse, Observable<Integer>>() {
#Override
public Observable<Integer> call(RpcDeviceInfoResponse rpcResponse) {
if (rpcResponse.getResult().size() == 0) {
errorReport(false, context.getResources().getString(R.string.error_while_getting_exp_points), "");
return null;
}
RpcDeviceInfoResponse.TaskListResult result = rpcResponse.getResult().get(0);
return Observable.from(result.getResult());
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<Integer>() {
#Override
public void onCompleted() {
Log.v("onCompleted", "onCompleted");
}
#Override
public void onError(Throwable e) {
Log.v("onError", "onError");
}
#Override
public void onNext(Integer integer) {
Log.v("onNext", "onNext");
}
});
I got stuck at the point 3. I have a list of integers and for each I need to execute a separate query.
You already have an Observable<Int> so you can use flatMap
Observable.defer(
//first query
).flatMap(
// convert result to Observable<Int>
)
.flatMap(
someInt -> doSomeQuery(someInt)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...)
On the change "SortBy", my program will do a NetworkIO to retrieve the top movies and display them.
However, it seems that though I have done subscribeOn(Schedulers.io()), the NetworkIO MovieDB.getPopular() and MovieDB.getTopRated() in the function call in map are excuted on the main thread and I get a android.os.NetworkOnMainThreadException.
I was wondering how to make the public Movie[] call(SortBy sortBy) asynchronous.
sortObservable.map(new Func1<SortBy, Movie[]>() {
#Override
public Movie[] call(SortBy sortBy) {
try {
switch (sortBy) {
case POPULAR:
return MovieDB.getPopular(); // NETWORK IO
case TOP_RATED:
return MovieDB.getTopRated(); // NETWORK IO
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return new Movie[0];
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});
Please check if the below works for you. It uses flatMap instead of map.
sortObservable.flatMap(new Func1<SortBy, Observable<Movie[]>>() {
#Override
public Observable<Movie[]> call(SortBy sortBy) {
try {
switch (sortBy) {
case POPULAR:
return Observable.just(MovieDB.getPopular()); // NETWORK IO
case TOP_RATED:
return Observable.just(MovieDB.getTopRated()); // NETWORK IO
}
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return Observable.just(new Movie[0]);
}
}).subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});
From your source code on Github, it seems like you are using synchronous mode of executing requests using OkHttp. OkHttp also supports asynchronous requests and that can be preferred. Below would be the changes required in few of the methods.
run method should consume enqueue instead of execute.
Observable<String> runAsync(String url){
return Observable.create(subscriber -> {
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) throws IOException {
subscriber.onNext(response.body().string());
}
#Override
public void onFailure(Call call, IOException e) {
subscriber.onError(e);
}
});
});
}
getApi can return an Observable<Movie[]> instead of Movie[]
public Observable<Movie[]> getApiAsync(String type){
return runAsync("http://api.themoviedb.org/3/movie/" + type
+ "?api_key=412e9780d02673b7599233b1636a0f0e").flatMap(response -> {
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(response,
new TypeToken<Map<String, Object>>() {
}.getType());
Movie[] movies = gson.fromJson(gson.toJson(map.get("results")),
Movie[].class);
return Observable.just(movies);
});
}
Finally I sort it out by myself:
sortObservable.flatMap(new Func1<SortBy, Observable<Movie[]>>() {
#Override
public Observable<Movie[]> call(SortBy sortBy) {
switch (sortBy) {
case POPULAR:
return Observable.fromCallable(() -> MovieDB.getPopular()).subscribeOn(Schedulers.io());
case TOP_RATED:
return Observable.fromCallable(() -> MovieDB.getTopRated()).subscribeOn(Schedulers.io());
default:
return Observable.fromCallable(() -> new Movie[0]).subscribeOn(Schedulers.io());
}
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Movie[]>() {
#Override
public void call(Movie[] movies) {
imageAdapter.loadData(movies);
}
});