I've recently started using Rxjava and retrofit, and looking for any ideas on how to perform n number of retrofit post calls and track them via rxjava. Once all actions have been completed a UI event will then occur.
I found this article: http://randomdotnext.com/retrofit-rxjava/ however it uses a for loop for initiating multiple request observables. Maybe there is a more elegant way besides a for loop? What is the best rxjava operator for this kind of effort?
Instead of using for loop, you can create an Observable sequence from the List/Array then use flatMap/concatMap operator.
Using for loop:
GithubService service = ServiceFactory.createRetrofitService(GithubService.class, GithubService.SERVICE_ENDPOINT);
for(String login : Data.githubList) {
service.getUser(login)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Github>() {
#Override
public final void onCompleted() {
// do nothing
}
#Override
public final void onError(Throwable e) {
Log.e("GithubDemo", e.getMessage());
}
#Override
public final void onNext(Github response) {
mCardAdapter.addData(response);
}
});
}
Pure Rx:
GithubService service = ServiceFactory.createRetrofitService(GithubService.class, GithubService.SERVICE_ENDPOINT);
Observable.from(Data.githubList)
.flatMap(login -> service.getUser(login))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);
RxJava provides a lot operators to combine multiple observables.
In your situation, you can use operator merge, and do UI work at onComplete()
When multiple call depend on the same thing you can use flat map or concat map to utilize your call. Then finally update your view.
Use the zip operator.
For Example :
you have 3 Retrofit Api and they are all return a string , and what you need is a long string merge by the 3 string.
So you need wait for the 3 api call are all return . and merge the return string with zip operator.
Code will be like:
Observable.zip(
api1,
api2,
api3,
(resp1, resp2, resp3) -> resp1 + resp2 + resp3
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(resp -> {
// do something
});
Related
TL;DR: I want to execute multiple Calls (Retrofit) like you can .zip() multiple Observables (RxJava2).
I have a retrofit2 function:
#GET("/data/price")
Call<JsonObject> getBookTitle(#Query("id") String id, #Query("lang") String lang);
I can execute it (async) in code with enquene():
ApiProvider.getBooksAPI().getBookTitle(bookId, "en").enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) { }
#Override
public void onFailure(Call<JsonObject> call, Throwable t) { }
});
Now I want to execute multiple Calls at once (get multiple book titles) and be notified when all requests are done. Here is when I am missing knowledge.
I know I could start using Observable (RXJava2) instead of Call (Retrofit2):
#GET("/data/price")
Observable<JsonObject> getBookTitle(#Query("id") String id, #Query("lang") String lang);
and then merge calls like in below example. But this code seems much more complex and long (especially if I only need 1 book title). Isn't there any way I could merge Calls without using Observable?
List<Observable<JsonObject>> mergedCalls = new ArrayList<>();
mergedCalls.add(ApiProvider.getBooksAPI().getBookTitle(bookId1, "en"));
mergedCalls.add(ApiProvider.getBooksAPI().getBookTitle(bookId2, "en"));
mergedCalls.add(ApiProvider.getBooksAPI().getBookTitle(bookId3, "en"));
Observable<List<JsonObject>> observable = Observable.zip(calls, responses -> {
// merge responses, return List
...
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
observer = new DisposableObserver<List<JsonObject>> () {
#Override
public void onNext(List<JsonObject> result) { // got all API results }
#Override
public void onError(Throwable e) { }
#Override
public void onComplete() { }
};
observable.subscribe(observer);
Using RxJava is the easy way of merging Retrofit Calls. Merging Calls manually by enqueuing all Calls and doing something when all of them invoke onResponse, will probably be more complex than simply using Observable.zip(...).
The other choice that you have is using Kotlin coroutines (now Retrofit has out of the box support for them). But that depends on the Kotlin presence in your code and your willingness of using coroutines.
EDIT:
(Answering your question from the comment)
If you really think about Calls and RxJava Observables you don't really have to do anything more when using RxJava. When using raw Calls you still have to:
Make sure you're on the right thread if you want to touch Views (observeOn(AndroidSchedulers.mainThread()))
Make sure you're touching network on the right thread (subscribeOn(Schedulers.io()))
Make sure you're not using the response when your Activity/Fragment/Something else is no longer present (disposing of the Disposable in RxJava handles that)
You can significantly simplify your example:
Don't create Observable & Observer. Simply use the subscribe method which returns Disposable. And then maintain just this one Disposable.
You probably don't need onComplete so you can use the simpler version of .subscribe(...)
You can remove the need for .subscribeOn(Schedulers.io()) by properly creating your RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()) when building the Retrofit instance.
BooksApi booksApi = ApiProvider.getBooksAPI();
List<Observable<JsonObject>> mergedCalls = new ArrayList<>();
mergedCalls.add(booksApi.getBookTitle(bookId1, "en"));
mergedCalls.add(booksApi.getBookTitle(bookId2, "en"));
mergedCalls.add(booksApi.getBookTitle(bookId3, "en"));
final Disposable disposable = Observable
.zip(mergedCalls, responses -> {
// merge responses, return List
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(list -> {
// got all API results
}, throwable -> {
});
Doing that for one call would be as simple as:
final Disposable disposable = booksApi
.getBookTitle(bookId1, "en")
.observeOn(AndroidSchedulers.mainThread())
.subscribe(title -> {
// got the result
}, throwable -> {
});
Here is my current code. The problem with this code is I need to wait get the data sequentially. The loading time is poor because of this. I want to use something like .enqueue() to get asynchronously several data at once, but I want to wait until I get all the data before continuing the process. Is it possible to do it with Retrofit?
List<Data> datas = new ArrayList<>();
for (long dataId : mDataIds) {
Response<T> response = resource.getData(dataId).execute();
if (response.isSuccessful()) {
datas.add(data.body());
}
}
//do something else
You can solve this problem very elegantly using RxJava.
If you never heard of RxJava before, it is a solution to many of your problems.
If you don't use java8 or retrolambda I recommend you to start using it, as it makes working with RxJava a piece of cake.
Anyway here's what you need to do:
// 1. Stream each value from mDataIds
Observable.from(mDataIds)
// 2. Create a network request for each of the data ids
.flatMap(dataId -> resource.getData(dataId))
// 3. Collect responses to list
.toList()
// Your data is ready
.subscribe(datas -> {}, throwable -> {});
1) First add RxJava2 dependencies to your project
2) Define retrofit api interface methods which return RxJava observable types
public interface DataApi {
#GET("dataById/")
Observable<Data> getData(#Query("id") String id);
}
3) Call api passing input data like below.
Observable.fromIterable(idList).subscribeOn(Schedulers.computation())
.flatMap(id -> {
return retrofitService.getData(id).subscribeOn(Schedulers.io());
}).toList().
.observeOn(AndroidSchedulers.mainThread()).subscribe( listOfData -> {// do further processing }, error -> { //print errors} );
For reference : http://www.zoftino.com/retrofit-rxjava-android-example
Define interface with callback Model type.
public interface LoginService {
#GET("/login")
Call<List<Login>> getLogin();
}
In you calling method override the callback method.
LoginService loginService = ServiceGenerator.createService(LoginService.class);
Call<List<Login>> call = loginService.getLogin();
call.enqueue(new Callback<List<Login>>() {
#Override
public void onResponse(Call<List<Login>> call, Response<List<Login>> response) {
if (response.isSuccessful()) {
// Login successful
} else {
// error response, no access to resource?
}
}
#Override
public void onFailure(Call<List<Login>> call, Throwable t) {
// something went completely south (like no internet connection)
Log.d("Error", t.getMessage());
}
}
I would recommend using RxJava and try it. You have something called FlatMap to combine the results.
To Start here is the tutorial start for RxJava2 and Retrofit2.
Hope you guys are doing well,
I have been working on a personal Android Project using RxJava and Retrofit. It's a search request for GitHub issues, I am getting input from the user when he clicks Search Button and using a PublishSubject object to emit the search text.
button.setOnClickListener(view -> {
publishSubject.onNext(editText.getText().toString());
});
and I am mapping this emit to an Observable using retrofit like this
publishSubject.concatMap(dataModel::getIssues)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::loadData, this::onError);
public Observable<List<Issue>> getIssues(String queryText) {
String[] query_params = queryText.split("/");
return gitHubApiService.getIssues(query_params[0], query_params[1], "open");
}
In result I am expecting List of Issues
public void loadData(List<Issue> issues) {
mProgressDialog.setVisibility(View.INVISIBLE);
if( issues.size() == 0) {
noIssueText.setVisibility(View.VISIBLE);
} else {
mRecyclerView.setVisibility(View.VISIBLE);
mIssuesList.clear();
mIssuesList.addAll(issues);
mAdapter.notifyDataSetChanged();
}
}
But my code seems to have some implementation issue Since it never emits anything from the network, not even on error is called.
I have tested the same example with the Observable I get from Retrofit API, so there is no retrofit error and so I think there is some problem with my concatMap logic.
Any help will be much appreciated
On first parse, I think that you might be making the network call in the main thread. Have you tried the following?
public Observable<List<Issue>> getIssues(String queryText) {
String[] query_params = queryText.split("/");
return gitHubApiService.getIssues(query_params[0], query_params[1], "open")
.subscribeOn(Schedulers.io());
}
Thing is, your onClickListener callback runs on the main thread, and there's no other context switch in the observable pipeline.
Let me describe my situation:
I want to register new records via an API.
I want to update some records via an API.
I need to be notified when all of these requests have finished, to start another task.
Specifically I have two ArrayList:
ArrayList<Report> createdReports = myHelper.getOfflineCreatedReports();
ArrayList<Report> editedReports = myHelper.getOfflineEditedReports();
Each report can use methods to get Observable instances from my ApiService (Retrofit implementation).
Observable<NewReportResponse> createdReportsObs = Observable.from(createdReports) // .just() != .from()
.flatMap(new Func1<Report, Observable<NewReportResponse>>() {
#Override
public Observable<NewReportResponse> call(Report report) {
return report.postToServer();
}
});
Observable<NewReportResponse> editedReportsObs = Observable.from(editedReports)
.flatMap(new Func1<Report, Observable<NewReportResponse>>() {
#Override
public Observable<NewReportResponse> call(Report report) {
return report.updateInServer();
}
});
I am using the flatMap operator to get one Observable for each report.
But I am not sure how to wait until all of the requests have finished.
I was thinking in using the zip operator.
Observable.zip(createdReportsObs, editedReportsObs, new Func2<NewReportResponse, NewReportResponse, Boolean>() {
#Override
public Boolean call(NewReportResponse justOneResponse, NewReportResponse justOneResponse2) {
return false;
}
});
Unfortunately I saw some examples where zip is used to create pairs of Observables.
Please suggest me what operator I can use to achieve it. Or how to do it using rxJava with a different approach.
Thank you in advance.
Are you using RxJava 2? If so you can use the new completable api. This is assuming you don't need to know any of the server results, just need to wait for them to complete.
Completeable.merge(createdReportsObs.toCompleteable(),
editedReportsObs.toCompleteable())
.subscribe()
This is my way. May not best practice.
Observable.merge(createdReportsObs, editedReportsObs)
.toList()
.flatMap(Observable::from)
.xxx //Now they are completed, do what you want
.subscribe();
I am new at RxJava and I have some pain to execute my first 'difficult' query.
I have two Observables generated from Retrofit, one that 'ping' a new api, the other the old one. The first one will query 'http://myurl.com/newapi/ping', the second one 'http://myurl.com/oldapi/ping'. Result from this request doesn't matter, I just want to know if the server is using the new or old api.
So I would like to call both observables at the same time, and finally have a boolean at the end to know if I'm using old or new api.
I tried something like that
Observable.mergeDelayError(obsOldApi,obsNewApi)
.observeOn(AndroidSchedulers.mainThread(), true)
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String s) {
}
});
But onError will be called once (I would like it to be called only if both request failed) and when onNext is called, I don't know from which request it came (old or new api ?).
Thank you for you help
For simplicity, let say that you'll received "NEW" or "OLD" regarding which api is available.
The difficulty of your operation is to manage errors : RxJava deals errors as terminal state. So you'll have to ignore this error, using .onErrorResumeNext() for example.
Observable<String> theOld = oldApi.map(r -> "OLD")
// ignore errors
.onErrorResumeNext(Obervable.empty());
Observable<String> theNew = newApi.map(r -> "NEW")
.onErrorResumeNext(Obervable.empty());
Observable.merge(theOld, theNew)
.first() // if both api are in errors
.subscribe(api -> System.out.println("Available API : "+api));
I added the operator first : it will take only the first result ("OLD" or "NEW") but trigger an error if the previous Observable is empty, which is the case if both API are unavaible.