I cant find a way to combine or chain a list of observables that it´s responses are prerequisites to other call that creates another Observable.
I´m using retrofit with observables.
My Service:
String url = "/geocode/json?sensor=false";
#GET(url)
Observable<GeocodeResult> getReverse(#Query("key") String gMapsKey,
#Query("latlng") LatLng origin);
And another service needs that GeocodeResult
#POST("/api/orders")
Observable<Order> createOrder(#Body GeocodeResult newOrder);
And I´m trying with:
// Prerequisite 1
Observable geocodeObservable = Address.get(...);
// Call createOrder after geocode is obtained?
return Observable.combineLatest(geocodeObservable, geocode -> createOrder(geocode));
But it don´t work because combineLatest needs an object, not an observable but I need to return the observable.
With JoinObservable:
Pattern5<Geocode> pattern = JoinObservable.from(geocodeObservable)
Plan0<Observable<Order>> plan = pattern.then(Order::create);
return JoinObservable.when(plan).toObservable().toBlocking().single();
But it throws an NoSuchElementException exception. Why?
I do toBlocking().single() because I need the Observable and not the Observable<Observable<Order>> :(.
Or how can I do it?
You could try using flatMap which can take the second observable as an parameter.
The function takes the items emitted by the first observable and creates an observable for each of those items and then flattens the items emitted by those observables into a single observable. This sounds complex, but fortunately both your Retrofit functions emit only a single item, so only one observable gets "flattened" into a observable.
You can use flatMap like this:
restApi.getReverse(gMapsKey, origin)
.flatMap(geocodeResult -> createOrder(geocodeResult))
.subscribe(order -> doSomething(order));
combineLatest doesn't really fit your needs, because it would perform both REST calls at the same time, not one after the other, so you can't use the response of the first one as the parameter of the second. I can't comment on why the exception gets thrown for JoinObservable because it's not a part of any public API. Also toBlocking() shouldn't really be used for anything other than testing.
I ended up creating a new Object and using Observable.combineLatest to combine all the prerequisites creating a new Observable and then using flatMap to create the new Observable from that observable.
Observable<NewOrderWrapper> newOrderObservable = Observable.combineLatest(prerequisites, (param1, param2,...) -> {return new NewOrderWrapper(param1, param2,...)});
and then
Observable<Order> finalOrderObservable = newOrderObservable.flatMap(newOrderWrapper -> create(newOrderWrapper))
Check a post here MakinGIANST/RXJava post.
Thanks to #LukaCiko
Related
I'm quite new to reactive programming and I've introduced myself to RxJava2 in Android. For the time being, I've faced easy problems, such as zipping observables. But now, something new cropped up, I'm trying to explain.
Suppose I've got a list of requests Observable<List<Request>>. What I want to do is to call a web service which returns per each request, the list of routes (wrapped in an Observable). I've checked questions like this, but in this case I think I can't flatMap an observable and a list of observables.
How can I do it? Is there any other operator?
You can flatten the Observable<List<Request>> into Observable<Request> using flatMapIterable. Assuming you have a helper method with the signature Observable<List<Route>> getListOfRoutes(Request request) { ... } you can do this:
Observable<List<Request>> obs = ...;
obs.flatMapIterable(l -> l)
.flatMap(request -> getListOfRoutes(request)
.doOnNext(routes -> request.setRoutes(routes))
.map(ign -> request)
)
...
This is assuming that you ultimately want Observable<Request> to be emitted downstream. If you want a different type, you can do something different in the map operator to suit your needs.
I'm refactoring the implementation of my repositories using RxJava so i want to know some ways to edit, for example, a user.
My getUser(email: String), with email as id, is returning an observable and in the repository implementation i either get the data from database or server, all good by now.
What i want to achieve is editing a user. For that i would have and update(user: User) function, and the naive way to use it would be
userRepository.getUser(email)
.subscribeOn(Schedulers.io())
.subscribe { user ->
user.name = "antoher name"
userRepository.update(user)
.subscribeOn(Schedulers.io())
.subscribe {
//handle response
}
}
Is there a way to avoid this type of call of an observer inside an observer? It is not very readable for me and i guess there's a better way but i'm not getting it.
NOTE: I'm using clean architecture, so i think an update for every field, making me get user in data module is not correct as i would have subscribe to an observer in data, and that difficult the dispose when activity destroys
For me is not the same question as When do you use map vs flatMap in RxJava? because, despite of flatMap being the thing that answer the question, it is not the same question, so anyone who has the same problem/question but don't know that flatmap is the answer, will never reach to use flatmap.
One strength of using RxJava is that you can chain as many async operations (method that would return Observable or Single, repository methods in your case) as you want without falling into callback hells. You see in your code that there are nested subscribe blocks. What if you had to chain more async network operations? You fall into callback hells and the code will become harder to follow and maintain.
Removing nested callbacks and making code more functional, compositional, and readable is one thing RxJava is really good at. In the intro part of ReactiveX website , they mention about this in the intro part of ReactiveX website (http://reactivex.io/intro.html).
Callbacks solve the problem of premature blocking on Future.get() by
not allowing anything to block. They are naturally efficient because
they execute when the response is ready.
But as with Futures, while callbacks are easy to use with a single
level of asynchronous execution, with nested composition they become
unwieldy.
Flatmap operator is to the rescue here. You can look into the definition of flatMap operator in the link below.
http://reactivex.io/documentation/operators/flatmap.html
Below is the code I would use in your case.
userRepository.getUser(email)
.subscribeOn(Schedulers.io())
.map { user -> user.name = "another name"; return user; }
.flatMap { user -> userRepository.update(user) }
.doOnSuccess { /* handle response here */ } // doOnNext if you are using observable
.subscribe({ /* or handle response here */ }, { /* must handle error here */})
Flatmap operator flattens Single of update response which will be returned by your repository's update method and pass just the response downstream. Above code is not only easier to read but also makes your code reusable because update logic is now part of the chain.
Distinguishing between map and flatMap is really important in exploiting the full benefit of RxJava so it will be really beneficial to get used to it!
I recently learned to use merge where I combined 4 API requests into one output. (Reference: https://stackoverflow.com/q/51262421/1083093)
Restapi.class
/************/
#GET("app/dashboard")
Observable<CategoryHomeModel[]> getCategories(#HeaderMap Map<String, String> headers);
#GET("app/wallet/balance")
Observable<WalletBalance> getWalletBalance(#HeaderMap Map<String, String> headers);
#GET("/app/swap/myrateswaps")
Observable<SwapSettings> rateMySwap(#HeaderMap Map<String, String> headers);
#GET("/app/getsettings")
Observable<Settings> getSettings(#HeaderMap Map<String, String> headers);
/************/
I have Four observables
Observable<CategoryHomeModel[]> categoriesObservable = retrofit
.create(Restapi.class)
.getCategories(prepareHeaders())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable<WalletBalance> walletObservable = retrofit
.create(Restapi.class)
.getWalletBalance(prepareHeaders())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable<Settings> settingsObservable = retrofit
.create(Restapi.class)
.getSettings(prepareHeaders())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable<SwapSettings> ratingsObservable = retrofit
.create(Restapi.class)
.rateMySwap(prepareHeaders())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Answer I found was useful using Merge function
Extending the Question above:
I have a similar question: How can I wait for first Observable to
complete moving to second to third to fourth. Since I am using a
variable obtained from first observable to pass it into second and so
on till fourth observable
CONCAT - I searched is a good way to achieve this i think.
How to use CONCAT as i have used MERGE above ?
What you need is FLATMAP operator. It maps items emitted by the observable into new observables. It's convenient to use for Retrofit observables, where only one result item may be emitted by the observable. So we can transform observable item (request result) into observable representing next request in the sequence.
Referring to original question observable samples the code could look like this (assuming lamda syntax as in Java 8/RetroLambda):
categoriesObservable.flatMap(categoryHomeModel -> {
/* Work on categoryHomeModel result; you can use it to configure next request */
return walletObservable.flatMap(walletBalance -> {
/* Work on walletBalance result; you can use it to configure next request */
return settingsObservable.flatMap(settings -> {
/* Work on settings result; you can use it to configure next request */
return ratingsObservable;
});
});
}).subscribe(swapSettings -> { /* Work on swapSettings; sequence is done */ });
This will make sure your requests are performed in sequence. Even if you use CONCAT, the results will be emitted to subscriber in sequence, but the actual requests may start in parallel, which sometimes is not expected. CONCAT/ZIP are good options if you have truly independent requests (e.g. querying different kinds of data), and it's ok (and even desirable) to run requests in parallel, when order of actual HTTP requests does not matter.
I have a case when I have multiple observables, each observable has its own implementation, they may be with the same type, or different I didn't decide know yet, but let's assume it they're the same type.
Observable<String> source1;
Observable<String> source2;
Observable<String> source3;
Observable<String> source4;
what I need to do now is to execute only one of them, so stream only move to the next observable if the previous one failed.
Some potential solutions:
the onErrorResumeNext() which it may be good if they're only two
observables, but in my case here, if I need to change the order of execution it will hard to update each observable.
there is the combineLatest but I don't know if it behaves the way I
described, or what modification to make work as I need.
how to achieve something like this and if they're with different types, what I need to do?
I don't know if there is better way to do it, but I would just use onErrorResumeNext() with the help of some methods for making it flexible:
Observable<String> buildObservable(Observable<String> obs, Observable<String>... subsequentObservables) {
Observable<String> observable = obs;
for (int i = 0; i < subsequentObservables.length; i++) {
observable = concatErrorObservable(observable, subsequentObservables[i]);
}
return observable;
}
where concatErrorObservable is:
Observable<String> concatErrorObservable(Observable<String> observable, Observable<String> observable2) {
return observable.onErrorResumeNext(observable2);
}
So you just need to provide the list of Observable to the buildObservable method. For example:
buildObservable(Observable.error(new Throwable("error!!")),
Observable.just("observable2"),
Observable.just("observable3"))
.subscribe(s -> Log.d(TAG, "result: " + s));
will print observable2 (in the logcat) because the first observable throws an error.
About the different types, you probably need a different map for each Observable, because I think your consumer (observer) will just expect one type of emitted data.
You can get a combined observable using onErrorResumeNext and reduce like this:
Observable<String> buildObservable(List<Observable<String>> observables) {
return Observable.fromIterable(observables)
.reduce(Observable::onErrorResumeNext)
.flatMapObservable(obs -> obs);
}
UPDATE:
To explain further, if you call the method with a list [o1, o2, o3], then
the fromIterable will return a higher-level observable equivalent to just(o1, o2, o3)
the reduce will combine the elements of this observable, sequentially calling onErrorResumeNext() with each element, like this:
o1 -> o1.onErrorResumeNext(o2) -> o1.onErrorResumeNext(o2).onErrorResumeNext(o3),
resulting in a still "higher level" 1-element observable that is equivalent to just(o1.onErrorResumeNext(o2).onErrorResumeNext(o3)).
the flatMapObservable() line will replace this 1-element observable with its one and only element itself, which is o1.onErrorResumeNext(o2).onErrorResumeNext(o3) (without the just()).
This result implements the fallback mechanism you need.
I am trying to migrate rxjava to rxjava2.
Map<String, Task> mCachedTasks;
I can convert it to Observable<List<Task>> by using the following line of code. Observable.from(mCachedTasks.values()).toList()
However, I cannot convert it on rxjava2.
1- I tried the following code.
Observable.fromIterable(mCachedTasks.values()) but it returns Observable<Task>
2- I also tried to use fromArray method, such as Observable.fromArray(mCachedTasks.values().toArray()). That gives me Observable<Object>.
How can I convert Map<String, Task> to Observable<List<Task>> on rxjava2?
I can convert it to Observable> by using the following line of code. Observable.from(mCachedTasks.values()).toList()
That's an option, but you are unnecessarily flatting the list - the from(), you taking a list and emit each item in onNext() , and then collect it back as a list using toList(), which waits for all the emissions and then emit single onNext() with the collected list.
However, I cannot convert it on rxjava2. 1- I tried the following
code. Observable.fromIterable(mCachedTasks.values()) but it returns
Observable<Task>
fromIterable() it's RxJava2 equivalent to from() of RxJava1, you can use again toList() - but in RxJava2 you will get Single instead of Observable (which is more concise API).
2- I also tried to use fromArray method, such as
Observable.fromArray(mCachedTasks.values().toArray()). That gives me
Observable<Object>
That's because toArray() return array of Object not Task.
The solution is rather simple, you actually don't need RxJava for the transformation , you've got already an in-memory Map, so first convert it to a List (if you insisting on List specifically):
List<Task> tasksList = new ArrayList<>(mCachedTasks.values());
and then use just() operator to create an Observable that emit this list as it's single value:
Observable<List<Task>> taskListObservable = Observable.just(tasksList);
Observable.fromIterable(mCachedTasks.values()).toList().toObservable();
Another option is to create a lazy Observable:
Observable<List<Task>> taskListObservable = Observable.fromCallable(
//this will be called each time someone subscribes
() -> {
//do not forget the defensive copy
return new ArrayList<>(mCachedTasks.values());
}
);
This way mCachedTasks.values() will always get the fresh version and there will be no unnecessary flattening with toList() operator.
public Observable> getOrganizations() {
List<Organization> list=new ArrayList<>(mcached.values());
if(mcached!=null && !mCacheIsDirty){
//return your map as list
return Observable.just(list);
}else {
mcached=new LinkedHashMap<>();
}
}