RXJava2: correct pattern to chain retrofit requests - java

I am relatively new to RXJava in general (really only started using it with RXJava2), and most documentation I can find tends to be RXJava1; I can usually translate between both now, but the entire Reactive stuff is so big, that it's an overwhelming API with good documentation (when you can find it). I'm trying to streamline my code, an I want to do it with baby steps. The first problem I want to solve is this common pattern I do a lot in my current project:
You have a Request that, if successful, you will use to make a second request.
If either fails, you need to be able to identify which one failed. (mostly to display custom UI alerts).
This is how I usually do it right now:
(omitted the .subscribeOn/observeOn for simplicity)
Single<FirstResponse> first = retrofitService.getSomething();
first
.subscribeWith(
new DisposableSingleObserver<FirstResponse>() {
#Override
public void onSuccess(final FirstResponse firstResponse) {
// If FirstResponse is OK…
Single<SecondResponse> second =
retrofitService
.getSecondResponse(firstResponse.id) //value from 1st
.subscribeWith(
new DisposableSingleObserver<SecondResponse>() {
#Override
public void onSuccess(final SecondResponse secondResponse) {
// we're done with both!
}
#Override
public void onError(final Throwable error) {
//2nd request Failed,
}
});
}
#Override
public void onError(final Throwable error) {
//firstRequest Failed,
}
});
Is there a better way to deal with this in RXJava2?
I've tried flatMap and variations and even a Single.zip or similar, but I'm not sure what the easiest and most common pattern is to deal with this.
In case you're wondering FirstRequest will fetch an actual Token I need in the SecondRequest. Can't make second request without the token.

I would suggest using flat map (And retrolambda if that is an option).
Also you do not need to keep the return value (e.g Single<FirstResponse> first) if you are not doing anything with it.
retrofitService.getSomething()
.flatMap(firstResponse -> retrofitService.getSecondResponse(firstResponse.id)
.subscribeWith(new DisposableSingleObserver<SecondResponse>() {
#Override
public void onSuccess(final SecondResponse secondResponse) {
// we're done with both!
}
#Override
public void onError(final Throwable error) {
// a request request Failed,
}
});
This article helped me think through styles in how I structure RxJava in general. You want your chain to be a list of high level actions if possible so it can be read as a sequence of actions/transformations.
EDIT
Without lambdas you can just use a Func1 for your flatMap. Does the same thing just a lot more boiler-plate code.
retrofitService.getSomething()
.flatMap(new Func1<FirstResponse, Observable<SecondResponse> {
public void Observable<SecondResponse> call(FirstResponse firstResponse) {
return retrofitService.getSecondResponse(firstResponse.id)
}
})
.subscribeWith(new DisposableSingleObserver<SecondResponse>() {
#Override
public void onSuccess(final SecondResponse secondResponse) {
// we're done with both!
}
#Override
public void onError(final Throwable error) {
// a request request Failed,
}
});

Does this not work for you?
retrofitService
.getSomething()
.flatMap(firstResponse -> retrofitService.getSecondResponse(firstResponse.id))
.doOnNext(secondResponse -> {/* both requests succeeded */})
/* do more stuff with the response, or just subscribe */

Related

How to get rid of nested RxJava streams?

I have a chain of calls to internet, database and as result I show collected info to user. Now I have very ugly 3-level nested RxJava stream. I really want to make it smooth and easy to read, but I've stuck really hard.
I already read everything about Map, flatMap, zip, etc. Cant' make things work together.
Code: make api call. Received info put in database subscribing to another stream in onSuccess method of first stream, and in onSuccess method of second stream received from DB info finally shows up to user.
Dat Frankenstein:
disposables.add(modelManager.apiCall()
.subscribeOn(Schedulers.io())
.observeOn(mainThread)
.subscribeWith(new DisposableSingleObserver {
public void onSuccess(ApiResponse apiResponse) {
modelManager.storeInDatabase(apiResponse)
//level 1 nested stream:
disposables.add(modelManager.loadFromDatabas()
.subscribeOn(Schedulers.io())
.observeOn(mainThread)
.subscribeWith(new DisposableSingleObserver{
public void onSuccess(Data data) {
view.showData(data);
}
public void onError(Throwable e) {
}
}));
}
#Override
public void onError(Throwable e) {
}
}));
}
I already read everything about Map, flatMap, zip, etc. Cant' make things work together.
Well, you missed something about flatMap, because this is what it's for ;)
disposables.add(
modelManager.apiCall()
.subscribeOn(Schedulers.io())
.doOnSuccess((apiResponse) -> {
modelManager.storeInDatabase(apiResponse)
})
.flatMap((apiResponse) -> {
modelManager.loadFromDatabase()
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe((data) -> {
view.showData(data);
})
);
But if you use a reactive database layer like Room's LiveData<List<T>> support, then you can actually ditch the modelManager.loadFromDatabase() part.
flatMap means convert the result of one stream into another stream, it is likely what you want.
Because you have an Observable that emits a ApiResponse then you have another "source of Observables" that takes this ApiResponse and gives another Observable that you want to observe on.
So you may likely want something like that:
disposables.add(modelManager.apiCall()
.flatMap(apiResponse -> {
modelManager.storeInDatabase(apiResponse);
return modelManager.loadFromDatabas()
})
.subscribeOn(Schedulers.io())
.observeOn(mainThread)
.subscribeWith(new DisposableSingleObserver {
public void onSuccess(ApiResponse apiResponse) {
view.showData(data);
}
public void onError(Throwable e) {
}
})

passing responses between observables in RxJava/RxAndroid

I have this code :
getLocationObservable() // ---> async operation that fetches the location.
// Once location is found(or failed to find) it sends it to this filter :
.filter(location -> { // ---> I want to use this location in the the onNext in the end
after finishing some calculation here, I either return 'true' and continue
to the next observable which is a Retrofit server call, or simply
return 'false' and quit.
})
.flatMap(location -> getRetrofitServerCallObservable( location )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Observer<MyCustomResponse>() {
#Override
public void onSubscribe(Disposable d) {
_disposable = d;
}
#Override
public void onNext(MyCustomResponse response) {
// I want to be able to use the `location` object here
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
I want to be able to use the location object from line 3(first observable), in the "onNext" that is trigerred by the second observable.
I can't manage to work it out.. any help would be much appreciated.
Instead of
getRetrofitServerCallObservable( location )
you could map the result to be a Pair (from your favourite library) of the response and the location:
getRetrofitServerCallObservable( location ).map(response -> Pair.create(location, response))
Then, in your onNext, you'd be receiving Pair<Location,MyCustomResponse> instances.
If you don't want to use a Pair class, you could use Object[], but if you do, please don't tell me about it :P

Validating multiple asyncronous methods in GWT

I am looking at a code that I have to work on. And basically I have to add a validation to a listener of a button.
The code has already multiple validations. They are kind of set in a cascade.
The listener of the buttons calls an asyncCallBack method that if everything is ok, on the onsuccess part of the method calls for the next one, an that one on the next one, until it reaches the end and goes to the next page. I am not a fan of this approach because it is kind of messy. What would the best way to do that using best practices.
An example of the code:
Button btnOK = new Button("Aceptar");
btnOK.addListener(Events.Select, new Listener<ButtonEvent>() {
public void handleEvent(ButtonEvent e) {
myService.getInfo1(1, txt, "N",
new AsyncCallback<List<InfoService>>() {
public void onFailure(Throwable caught) {
// goes back
return
}
public void onSuccess(
List<Object> result) {
// do some validation with the result
validation2();
}
}
}
}
public void validation2(){
myService.getDireccionCanalesElectronicos(id, new AsyncCallback<MyResult>() {
public void onSuccess(MyResult result) {
// do some validation with the result
validation3();
}
...
}
}
public void validation3(){
myService.getDireccionCanalesElectronicos(id, new AsyncCallback<MyResult>() {
public void onSuccess(MyResult result) {
// do some validation with the result
validation4();
}
...
}
}
Is there a better way of doing this, it seems messy and hard to follow. Adding another validation is complicated. It doesnt seem like a good practice.
Create 1 method in the servlet that calls all the validation methods and do just one call in the client ?
public void validation()
{
boolean ok = validation1();
if (ok) ok = validation2();
return validation;
}
Using mirco services is sometimes hard to deal with. As #Knarf mentioned, this is a way to go. But sometime you may want to handle the calls on the client side. Another one will be using this tiny framework: sema4g. It will help you to solve your problem.
A solution might look like that:
First create the sem4g commands:
private SeMa4gCommand createGetInfoCommand() {
return new AsyncCommand() {
// create callback
MethodCallbackProxy<List<InfoService>> proxy = new MethodCallbackProxy<List<InfoService>>(this) {
#Override
protected void onProxyFailure(Method method,
Throwable caught) {
// Enter here the code, that will
// be executed in case of failure
}
#Override
protected void onProxySuccess(Method method,
List<InfoService> response) {
// Enter here the code, that will
// be executed in case of success
}
};
#Override
public void execute() {
// That's the place for the server call ...
myService.getInfo1(1, txt, "N", proxy);
}
};
}
do that for all your calls;
private SeMa4gCommand createCommandGetDireccionCanalesElectronicos() {
return new AsyncCommand() {
// create callback
MethodCallbackProxy<MyResult> proxy = new MethodCallbackProxy<MyResult>(this) {
#Override
protected void onProxyFailure(Method method,
Throwable caught) {
// Enter here the code, that will
// be executed in case of failure
}
#Override
protected void onProxySuccess(Method method,
List<MyResult> response) {
// Enter here the code, that will
// be executed in case of success
}
};
#Override
public void execute() {
// That's the place for the server call ...
myService. getDireccionCanalesElectronicos(id, proxy);
}
};
}
Once you have done this for all your calls, create a sema4g context and run it:
try {
SeMa4g.builder()
.addInitCommand(new InitCommand() {
#Override
public void onStart() {
// Enter here your code, that
// should be executed when
// the context is started
})
.addFinalCommand(new FinalCommand() {
#Override
public void onSuccess() {
// Enter here the code, that will
// be executed in case the context
// ended without error
}
#Override
public void onFailure() {
// Enter here the code, that will
// be executed in case the context
// ended with an error
})
.add(createGetInfoCommand())
.add(createCommandGetDireccionCanalesElectronicos())
.build()
.run();
} catch (SeMa4gException e) {
// Ups, something wrong with the context ...
}
For more informations, read the documentation. If you have questions, feel free to ask: SeMa4g Gitter room.
Hope that helps.

Exiting out of the iteratable Observable upon successful response

I have a scenario in which I've to bridge the nonreactive code with Reactive Code.
Consider the following scenario.
I have a list of 3 URLs in an ArrayList. I want to call each URL in the order they are inside the ArrayList. I can call only 1 URL at a time. If the first URL returns a successful Response, I want to call onComplete() and don't wanna execute the remaining URL. However, if the response is an error, I want to execute the next URL in the list. I don't want RxJava to call flatMap for the next URL unless I get an error response for the previous URL. Due to my primitive understanding of RxJava, I couldn't figure out a way to achieve this.
What I planned to do something like this:
Observable.fromIteratable(urlList)
.subscribeOn(Schedulars.io())
.flatMap(new Func(String url, String data) {
SomeNetworkLibrary.getData(url)
.OnResponse(new NewResponse() {
public void onSuccess(String dataFromInternet) {return dataFromInternet;}
public void onError(String errorMessage) {return errorMessage;)
})
// wait until we have response from the network call above and then return
// I don't know what will be the cleanest and efficient way of waiting here.
});
TLDR;
I don't want flatMap() to be called before the results from the previous flatMap() have been returned.
How can I do that?
You can turn the network api call into an Observable and then use take(1) after the flattening:
Observable.fromIteratable(urlList)
.subscribeOn(Schedulars.io())
.concatMapDelayError((String url, String data) -> {
return Observable.create(emitter -> {
SomeNetworkLibrary.getData(url)
.OnResponse(new NewResponse() {
public void onSuccess(String dataFromInternet) {
emitter.onNext(dataFromInternet);
// don't call emitter.onComplete() so that
// concatMapDelayError doesn't switch to the next source
}
public void onError(String errorMessage) {
emitter.onError(errorMessage);
}
);
});
// wait until we have response from the network call above and then return
// I don't know what will be the cleanest and efficient way of waiting here.
})
.take(1);

RxJava callback for last onNext() execution finished

I'm new to RxJava. I would like to download some data for each TempoAccount entity from given collection and store it all in a map accountsWithProjects. When the code of last onNext(TempoAccount tempoAccount) is completed I'd like to call filterAccountsWithProjects(accountsWithProjects) method. Is there some simple way to achieve it?
private void getProjectsForEachTempoAccount(Collection<TempoAccount> tempoAccounts) {
final Map<TempoAccount, Collection<TempoProject>> accountsWithProjects =
new HashMap<>(tempoAccounts.size());
Observable<TempoAccount> accountsObservable = Observable.from(tempoAccounts);
accountsObservable
.compose(ObservableUtils.applySchedulers())
.subscribe(new ObserverAdapter<TempoAccount>() {
#Override
public void onError(Throwable e) {
view.notifyAboutError(e.getMessage());
}
#Override
public void onNext(TempoAccount tempoAccount) {
jira.requestProjectsInfoForTempoAccount(String.valueOf(tempoAccount.getId()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new ObserverAdapter<Collection<TempoProject>>() {
#Override
public void onError(Throwable e) {
view.notifyAboutError(e.getMessage());
}
#Override
public void onNext(Collection<TempoProject> projects) {
accountsWithProjects.put(tempoAccount, projects);
}
});
}
#Override
public void onCompleted() {
filterAccountsWithProjects(accountsWithProjects);
}
});
}
Problem: In the code above filterAccountsWithProjects(accountsWithProjects) is fired before all observables from onNext(TempoAccount tempoAccount) are completed.
Edit:
I want to create an Observable of such a type: Observable<Map<TempoAccount, Collection<TempoProject>>.
I have two observables given:
Observable<TempoAccount> accountsObservable = Observable.from(tempoAccounts)
Observable<Collection<TempoProject>> projectsForAccountObservable = jira.requestProjectsInfoForTempoAccount(TempoAccount account)
So my questions is: can I connnect them somehow and create the map having these two observables.
You should use the flatMap() function on your original stream to do the things you are currently doing in the onNext(). Also, you don't need to filter the stream in onComplete(). You could use filter() on the stream itself and deal with the problem in a more "Reactive" way.
Here is an example:
accountsObservable
.compose(ObservableUtils.applySchedulers())
.map(tempoAccount -> new Pair<TempoAccount, Collection<TempoProject>>(tempoAccount, fetchInfoAccountForTempoAccount(tempoAccount)))
.filter(pair -> hasProjects(pair))
.toMap(pair -> pair.first(), pair -> pair.second)
.subscribe(...)
EDIT:
Updated the suggested answer - you get the TempoAccounts, then you map each account to a Pair of account and collection of TempoProjects. You filter the pairs to see if you have any projects and then you use toMap() to create your desired result. Be aware, that to get toMap() working, your observables have to call onComplete() when the end of the stream is reached.

Categories

Resources