Send multiple requests using RxJava - android

I am very new to using RxJava with Retrofit in Android. I have successfully written the API calls and developed the interface too. Now, I want to write my code in a way that I can send two requests: second request depending upon the values of first request. Can someone guide me if this is possible? If so then how? Any code snippet will really be helpful.
For example: following are two requests:
mCompositeDisposable.add(fcService.getStationList()
.subscribeOn(Schedulers.io()) // "work" on io thread
.observeOn(AndroidSchedulers.mainThread()) // "listen" on UIThread
.subscribe(this::handleResults, this::handleError)
);
mCompositeDisposable.add(fcService.getStationSensor("12345678")
.subscribeOn(Schedulers.io()) // "work" on io thread
.observeOn(AndroidSchedulers.mainThread()) // "listen" on UIThread
.subscribe(this::handleResults, this::handleError)
);
Second request is possible with the value from the first request's response. Is it possible to merge these two requests in a way that I write code only once for them?

With the flatMap operator you can check the response of the first call and choose the next action to follow, in this way you build a new Observable that you can subscribe to (The next "code" is kotlin style):
Single<StationSensor> newSingle =
fcService.getStationList().flatMap{ stationList ->
when(stationList){
"OK_value" -> fcService.getStationSensor(stationList)
else -> Single.error(RuntimeException("Error response"))
}
}

Related

Best way to do an RxJava call based on the results of the previous API call

I'm doing an API call to get the descriptions of a program podcast and based on the type of podcast, I may or may not have to do another API call to get more information. I'm new to RxJava and I'm not sure the best way to make such calls. Here's what I have so far:
public void fetchFeaturedItems() {
Timber.i("Fetching Featured...");
disposables.add(episodeService.getFeatured()
.subscribeOn(Schedulers.io())
.doOnNext(featured -> { //make second call
final Episode episode = featured.getEpisode();
Observable<Timing> timingObservable = episodeService.getTimingForEpisodeActs(episode);
if (timingObservable != null) {
timingObservable
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe(timing -> {episodeManager.saveTiming(timing);}); //save to database
}
})
.observeOn(Schedulers.io())
.subscribe(featured -> {
saveFeatured(featured);
final Episode episode = featured.getEpisode();
notificationManager.handleNewEpisodeNotification(episode);
}, Timber::e));
}
This all works, but I'm getting a "result of subscribe is not used" lint warning on the second subscribe. I'm not combining results of the two calls. I could really use some guidance.
Use flatMap() instead of onNext(). You get warning about "result of subscribe is not used" cause of second subscribtion. flatMap() should help.
read this first and other RxJava documentation
.doOnNext is a side-effect operator. What you're doing:
timingObservable
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe(timing -> {episodeManager.saveTiming(timing);});
Will just create a disposable. This disposable won't be a part of the stream. Also note that the timingObservable stream will now run totally independently, because as I just said, doOnNext is a side-effect operator. What you're doing is a fire-and-forget call. To make the response as a part of the stream, use a .flatMap in place of .doOnNext. It will merge your responses as they come, and push them to the downstream operators.
Ideally, a reactive stream should be subscribed to only once - you are doing it twice. This is an immediate code smell.

Editing data in repository pattern using RxJava

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!

How to create Observables and update them with Retrofit later RxJava?

Basically I need to create a List of Observables without initial values. I subscribe to that list of Observables and will provide required results based on the responses from all Observables. I use zip operator.
The problem is that I need to create Observables initially, add them to the list and use zip operator. Only later I do network request with Retrofit and I need to update the value of observable in the list so my whole zip operator will be working.
However, I did not find a way to force update an observable in the list with the response from Retrofit. It seems very easy but I did not found any solutions.. only idea is to use tons of subjects and add them to the list instead...
List<Observable<Object>> listObservables = new ArrayList<>();
//Adding initial request
Observable<Object> testObservable = RetrofitFactory.create().startProcess();
listObservables.add(testObservable);
Observable.concatDelayError(listObservables).subscribe(response ->
{
//This is where all results should be managed
Log.d("response", String.valueOf(response));
},
error ->
{
Log.d("response", String.valueOf(error));
});
//Actual request occurs much later in application
listObservables.get(0).subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread()).subscribe(response ->
{
// the response of this, should notify concatDelayError
Log.d("respoonse", String.valueOf(response));
});
If I understand correctly, you want to implement sub requests model. For this task you can break chain of operators to different execution flows and combine it back, for example, with zip operator. With this approach you can create completely independent data flow with sole trigger.
Subject<Event> eventSubject = PublishSubject.create();
Observable<TriggerObject> mainRequest = eventSubject.flatMap((event) ->
RetrofitFactory.create().startProcess());
Observable<FirstSubResult> firstSubRequest = mainRequest.flatMap(tigger -> {
// make first sub request
});
Observable<SecondSubResult> secondSubRequest = mainRequest.flatMap(tigger -> {
// make second sub request
});
Observable<CompleteResult> resultObservable = Observable.zip(firstSubRequest, secondSubRequest,
// zip function
(first, second) -> {
// combine result of sub requests to complete result object
});
Now you can start request flow by your event:
// post your event. On button clicked for evxample
eventSubject.doOnNext(yourEvent);
NOTE: this answer show main idea of chaining data flow sequences. This applicable to to other types of requests, and you can use this approach without retrofit

Conditional chain of observables

I want to asynchronously retrieve data via multiple REST APIs. I'm using Retrofit on Android with the rxJava extension, i.e. I execute any GET request by subscribing to an Observable.
As I said, I have multiple source APIs, so when the first source does not yield the desired result I want to try the next on, if that also fails, again try the next and so forth, until all sources have been queried or a result was found.
I'm struggling to translate this approach into proper use of Observables since I don't know which operators can achieve this behaviour and there are also some constraints to honor:
when a result has been found, the remaining APIs, if any, should not be queried
other components depend on the result of the query, I want them to get an Observable when starting the request, so this Observable can notify them of the completion of the request
I need to keep a reference to aforementioned Observable because the same request could possibly be made more than once before it has finished, in that case I only start it the first time it is wanted and subsequent requests only get the Observable which notifies when the request finished
I was starting out with only one API to query and used the following for the request and subsequent notification of dependent components:
private Observable<String> loadData(int jobId) {
final ConnectableObservable<String> result = Async
.fromCallable(() -> getResult(jobId))
.publish();
getRestRequest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
dataHolder -> {
if (dataHolder.getData() != null && !dataHolder.getData().isEmpty()) {
saveData(dataHolder.getData());
} else {
markNotFound(dataHolder);
}
},
error -> currentJobs.remove(jobId),
() -> {
currentJobs.remove(jobId);
result.connect();
});
return result;
}
This code was only called for the first request, the returned Observable result would then be saved in currentJobs and subsequent requests would only get the Observable without triggering the request again.
Any help is highly appreciated.
Assuming you have a set of observables that re-connect each time you subscribe:
List<Observable<Result>> suppliers = ...
Then you just need to do the logical thing:
Observable<Result> results = Observable
.from(suppliers)
.concatMap(supplier -> supplier)
.takeFirst(result -> isAcceptable(result))
.cache()
Use .onErrorResumeNext, and assuming that each service observable may return 0 or 1 elements use first to emit an error if no elements are emitted:
Observable<T> a, b, c;
...
a.first().onErrorResumeNext(t -> b.first())
.onErrorResumeNext(t -> c.first())
.onErrorResumeNext(t -> d.first())
...

RxJava linear backoff, passing in earlier date parameters on retry

I'm refactoring some code that would call a web service with a formatted date parameter ("2016-3-10" for example), and if that returned null, would fire off another method with a date one day earlier (like "2016-3-9"). This would happen for at least 3 retries.
I'm refactoring this into RxJava and am not sure how to implement the backoff strategy or which .retry() or .retryWhen() operator to use in this situation.
I have the web service returning the usual Observable using Retrofit.
This is what I have currently:
PictureService pictureService = retrofit.create(PictureService.class);
pictureService.getPhotos(date)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry() //Some retry
...
.subscribe()
So which operator would be best used here for retrying the requests, and how should I pass in a new date to the getPhotos() request when the current date returns null?
Just for the sake of completion here is the Retrofit service that was mentioned above:
#GET("/someurl/photos?api_key=xxxxxxx")
Observable<PictureAPI> getPhotos(#Query("earth_date") String earthDate);
OK, now that I can answer this question again, here we go:
Observable.just("2016-3-10", "2016-3-9", "2016-3-8")
.concatMap(date -> pictureService.getPhotos(date))
.filter(response -> response != null)
.take(1)...
For a detailed explanation take a look at this blog post by Dan Lew: http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/
In short: concat (and concatMap) will subscribe to each new Observable only after the previous one has emitted onCompleted AND only if more items are needed downstream. The take(1) will unsubscribe after the first non-null response and therefore concatMap will just not subscribe to the next Observable.
EDIT: To check that really only one request is sent you could
1.) Enable logging in Retrofit (if you are recent version by adding an Interceptor to the OkHttpClient). This lets you directly observe the requests that are being sent.
2.) Add a doOnSubscribe() like this:
.concatMap(date -> pictureService.getPhotos(date)
.doOnSubscribe(new Action0() {
#Override
public void call() {
Log.d(TAG, "sending request for " + date);
}
});
)
Since Retrofit requests are only sent upon subscription the log message will appear if and only if a request is then sent.

Categories

Resources