The Problem
I have two Apis. Api 1 gives me a List of Items and Api 2 gives me more detailed Information for each of the items I got from Api 1. The way I solved it so far results in bad Performance.
The Question
Efficent and fast solution to this Problem with the help of Retrofit and RxJava.
My Approach
At the Moment my Solution Looks like this:
Step 1: Retrofit executes Single<ArrayList<Information>>
from Api 1.
Step 2: I iterate through this Items and make a request for each to Api 2.
Step 3: Retrofit Returns Sequentially executes Single<ExtendedInformation>
for
each item
Step 4: After all calls form Api 2 completely executed I create a new Object for all Items combining the Information and Extended Information.
My Code
public void addExtendedInformations(final Information[] informations) {
final ArrayList<InformationDetail> informationDetailArrayList = new ArrayList<>();
final JSONRequestRatingHelper.RatingRequestListener ratingRequestListener = new JSONRequestRatingHelper.RatingRequestListener() {
@Override
public void onDownloadFinished(Information baseInformation, ExtendedInformation extendedInformation) {
informationDetailArrayList.add(new InformationDetail(baseInformation, extendedInformation));
if (informationDetailArrayList.size() >= informations.length){
listener.onAllExtendedInformationLoadedAndCombined(informationDetailArrayList);
}
}
};
for (Information information : informations) {
getExtendedInformation(ratingRequestListener, information);
}
}
public void getRatingsByTitle(final JSONRequestRatingHelper.RatingRequestListener ratingRequestListener, final Information information) {
Single<ExtendedInformation> repos = service.findForTitle(information.title);
disposable.add(repos.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(new DisposableSingleObserver<ExtendedInformation>() {
@Override
public void onSuccess(ExtendedInformation extendedInformation) {
ratingRequestListener.onDownloadFinished(information, extendedInformation);
}
@Override
public void onError(Throwable e) {
ExtendedInformation extendedInformation = new ExtendedInformation();
ratingRequestListener.onDownloadFinished(extendedInformation, information);
}
}));
}
public interface RatingRequestListener {
void onDownloadFinished(Information information, ExtendedInformation extendedInformation);
}
I solved a similar problem with RxJava2. Execution of requests for Api 2 in parallel slightly speeds up the work.
InformationRepository - just interface. Its implementation is not interesting for us.
FullInformation - container for result.
the
flatMap
operator is designed to cater to these types of workflows.i'll outline the broad strokes with a simple five step example. hopefully you can easily reconstruct the same principles in your code:
(incidentally, if the order of the emissions matter, look at using
concatMap
instead).i hope that helps.
Try using
Observable.zip()
operator. It will wait until both Api calls are finished before continuing the stream. Then you can insert some logic by callingflatMap()
afterwards.http://reactivex.io/documentation/operators/zip.html
tl;dr use
concatMapEager
orflatMap
and execute sub-calls asynchronously or on a schedulers.long story
I'm not an android developer, so my question will be limited to pure RxJava (version 1 and version 2).
If I get the picture right the needed flow is :
Assuming Retrofit generated the clients for
If the order of the item is not important, then it is possible to use
flatMap
only:But only if the retrofit builder is configured with
Either with the async adapter (calls will be queued in the okhttp internal executor). I personally think this is not a good idea, because you don't have control over this executor.
Or with the scheduler based adapter (calls will be scheduled on the RxJava scheduler). It would my preferred option, because you explicitly choose which scheduler is used, it will be most likely the IO scheduler, but you are free to try a different one.
The reason is that
flatMap
will subscribe to each observable created byapi2.extendedInfo(...)
and merge them in the resulting observable. So results will appear in the order they are received.If the retrofit client is not set to be async or set to run on a scheduler, it is possible to set one :
This structure is almost identical to the previous one execpts it indicates locally on which scheduler each
api2.extendedInfo
is supposed to run.It is possible to tune the
maxConcurrency
parameter offlatMap
to control how many request you want to perform at the same time. Although I'd be cautious on this one, you don't want run all queries at the same time. Usually the defaultmaxConcurrency
is good enough (128
).Now if order of the original query matter.
concatMap
is usually the operator that does the same thing asflatMap
in order but sequentially, which turns out to be slow if the code need to wait for all sub-queries to be performed. The solution though is one step further withconcatMapEager
, this one will subscribe to observable in order, and buffer the results as needed.Assuming retrofit clients are async or ran on a specific scheduler :
Or if the scheduler has to be set locally :
It is also possible to tune the concurrency in this operator.
Additionally if the Api is returning
Flowable
, it is possible to use.parallel
that is still in beta at this time in RxJava 2.1.7. But then results are not in order and I don't know a way (yet?) to order them without sorting after.Check below it's working.
Say you have multiple network calls you need to make–cals to get Github user information and Github user events for example.
And you want to wait for each to return before updating the UI. RxJava can help you here. Let’s first define our Retrofit object to access Github’s API, then setup two observables for the two network requests call.
Used Interface for it like below:
After we use RxJava’s zip method to combine our two Observables and wait for them to complete before creating a new Observable.
Finally let’s call the subscribe method on our new combined Observable:
No more waiting in threads etc for network calls to finish. RxJava has done all that for you in zip(). hope my answer helps you.