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 -> {
});
Related
I'm fairly new to RxJava, RxAndroid. I have two editText one for password and one for password confirmation. Basically I need to check if the two strings match. Is it possible to do this using Observables? Would really appreciate an example so I can grasp it. Cheers.
First, create Observable out of your EditText. You can utilize RxBinding library or write wrappers by yourself.
Observable<CharSequence> passwordObservable =
RxTextView.textChanges(passwordEditText);
Observable<CharSequence> confirmPasswordObservable =
RxTextView.textChanges(confirmPasswordEditText);
Then merge your streams and validate values using combineLatest operator:
Observable.combineLatest(passwordObservable, confirmPasswordObservable,
new BiFunction<CharSequence, CharSequence, Boolean>() {
#Override
public Boolean apply(CharSequence c1, CharSequence c2) throws Exception {
String password = c1.toString;
String confirmPassword = c2.toString;
// isEmpty checks needed because RxBindings textChanges Observable
// emits initial value on subscribe
return !password.iEmpty() && !confirmPassword.isEmpty()
&& password.equals(confirmPassword);
}
})
.subscribe(new Consumer<Boolean>() {
#Override
public void accept(Boolean fieldsMatch) throws Exception {
// here is your validation boolean!
// for example you can show/hide confirm button
if(fieldsMatch) showConfirmButton();
else hideCOnfirmButton();
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
// always declare this error handling callback,
// otherwise in case of onError emission your app will crash
// with OnErrorNotImplementedException
throwable.printStackTrace();
}
});
subscribe method returns Disposable object. You have to call disposable.dispose() in your Activity's onDestroy callback (or OnDestroyView if you are inside Fragment) in order to avoid memory leaks.
P.S. The example code uses RxJava2
You can use this library to do something like this.
Observable
.combineLatest(RxTextView.textChanges(passwordView1),
RxTextView.textChanges(passwordView2),
(password1, password2) -> checkPasswords))
.filter(aBoolean -> aBoolean)
.subscribe(aBoolean -> Log.d(passwords match))
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.
I need to iterate through a list of data, get all their Ids, trigger network calls using those Ids, and then do something once I get the list of results (The server could take list of Ids and return a list of result but it doesn't work that way as of now).
Currently I got it working like this:
for (Data data: dataList) {
String id = data.getId();
idObservables.add(dataService.getResultFromNetwork(id));
}
Observable.zip(idObservables, new FuncN<List<Result>>() {
#Override
public List<Result> call(Object... args) {
List<Result> resultList = new ArrayList<>();
for (Object arg : args) {
resultList.add((Result) arg));
}
return resultList;
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<Result>>() {
#Override
public void call(List<Result> resultList) {
// Do something with the list of Result
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.e("", "error", throwable);
}
});
But obviously I'm not happy with the way it's done. It will be great to know better ways to handle a case like this using RxJava in Android.
Cheers!!
Apologies for the lambda-isms, but it really makes the logic easier to read:
Observable
.fromIterable(dataList)
.flatMap(data ->
dataService
.getResultFromNetwork(data.getId())
.subscribeOn(Schedulers.io())
)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(list -> {
// do something
});
The idea is to keep as much as the pipeline in Rx-land; it's worth it to have simple methods taking normal parameters and return observables, and complex methods take observables and return observables.
Note: the above will not retain ordering; if you need an ordered list, use concatMap with prefetch.
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.
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
});