Retrofit chaining observables - android

I'm trying to use retrofit with rxjava. I have a problem chaining retrofit observables with one another or with observables created by me. Some example:
Observable<List<Friend>> friendsListObservable = friendsService.getFriends();
Observable<Void> updateReqestObservable = friendsListObservable.switchMap(friends -> {
Log.d(TAG, "Hello");
return userAPI.updateFriends(session.getUserId(), friends);
}).subscribe();
Everything gets called until it gets to switchMap. So hello is never displayed, but if I return for instance Observable.just(null) instead of the retrofit observable it works fine. Also if I user the retrofit observable without chaining, it works.
Edit1:
It's an android app. Seems like the map operator is not called at all. Sometimes it happens that with retrofit observables also. I still think that it has something to do with threading. From what I understand an operator is called when an item is emitted, but calling onNext doesn't trigger the map operator. Below is my whole code:
public Observable<List<FacebookFriend>> getFriends() {
PublishSubject<List<FacebookFriend>> friendsPublishSubject = PublishSubject.create();
Observable<List<FacebookFriend>> returnObservable = friendsPublishSubject.doOnSubscribe(() -> {
Log.d(TAG, "OnSubscribe called");
Session session = Session.getActiveSession();
if (session != null && session.isOpened()) {
new Request(session, "/me/friends", null, HttpMethod.GET,
new Request.Callback() {
public void onCompleted(Response response) {
JSONObject graphResponse = response.getGraphObject()
.getInnerJSONObject();
try {
JSONArray friends = graphResponse.getJSONArray("data");
Gson gson = new Gson();
Type listType = new TypeToken<ArrayList<FacebookFriend>>() {
}.getType();
List<FacebookFriend> friendsList = gson.fromJson(friends.toString(), listType);
friendsPublishSubject.onNext(friendsList);
friendsPublishSubject.onCompleted();
} catch (JSONException e) {
e.printStackTrace();
friendsPublishSubject.onError(e);
}
}
}).executeAsync();
} else {
InvalidSessionException exception = new InvalidSessionException("Your facebook session expired");
friendsPublishSubject.onError(exception);
}
});
return returnObservable.subscribeOn(AndroidSchedulers.mainThread()).observeOn(AndroidSchedulers.mainThread());
}
public Observable<Void> updateFriendsList() {
Observable<List<FacebookFriend>> facebookFriendsListObservable = facebookService.getFriends();
Observable<Void> updateReqestObservable = facebookFriendsListObservable.map(friends -> {
Log.d(TAG, "This is never called");
});
}

One way you could get around blocking at the actual calling point would be to subscribe it to a subject and then block on the subject at the end of whatever part of your code requires the requests to have been executed.
For example:
final ReplaySubject<Void> subject = ReplaySubject.create();
friendsService.getFriends()
.switchMap(friends -> userApi.updateFriends(session.getUserId(), friends))
.subscribeOn(Schedulers.io())
.subscribe(subject);
// Do other things that don't require this to be done yet...
// Wherever you need to wait for the request to happen:
subject.toBlocking().singleOrDefault(null);
Since a subject is also an Observable, you could even return the subject from a method, and block on it later.
All that being said, it does seem a bit odd to be using the update call as a side effect there. Is updateFriends also a Retrofit service? If so, you may want to consider making the update call a synchronous service that returns void instead of an Observable<Void> that you will call from within an onNext call. If it still needs to block, you can use forEach like this:
friendsService.getFriends()
.forEach(friends -> { userApi.updateFriends(session.getUserId(), friends) });
forEach is very similar to subscribe, except that it explicitly blocks. You could even use it with your original code, but added an empty onNext action wouldn't be terribly clean.
If you can also provide more details about your app structure (is this all in a main method? is it an Android app? etc.) I could give some more pointers on avoiding blocking as much as possible.

Related

Should I use Observable(RxJava2) or Call (retrofit2)?

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 -> {
});

Is it possible to load several data asynchronously with Retrofit and wait for all data before doing something else?

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.

RxJava concatMap no response

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.

Get one result from 2 observables with RxJava

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.

Chaining two web service calls using RxJava and Retrofit

I am using RxJava and Retrofit.My basic requirement is,i want to chain two api calls, which will get called one after one another. Response received from first api is used as input while calling second api. After reading some stuff on internet i used to flatmap to achieve this. While carrying out this operation i am showing loader.Sometimes it runs smoothly but on some occasions this loader freezes. DDMS shows log of "skipped 300 frames,Application may be doing too much work on its main thread". I suspect one of my network call is running on main thread. I am not able to figure out how to chain these two calls so that they can be smoothly called in background without hampering my main thread. Any help is greatly appreciated . Thanks in advance
This is what i have tried so far
private CompositeSubscription mSubscriptions = new CompositeSubscription();
Subscription subscription = Observable.just(getAddress())
.subscribeOn(Schedulers.newThread())
.flatMap(address -> mPlatformApi.secondWebService(address.getLatitude(),address.getLongitude())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(modelTwo ->
{
//updating My ui
}, throwable -> {
//Error Handling
});
mSubscriptions.add(subscription);
private android.location.Address getAddress(){
String addressString = "";//some Address String
Geocoder coder = new Geocoder(getActivity());
android.location.Address address=null;
try {
ArrayList<android.location.Address> addressList = (ArrayList<android.location.Address>) coder.getFromLocationName(addressString, 1);
if(addressList !=null && addressList.size()>0) {
address = addressList.get(0);
} else {
}
} catch (IOException e) {
e.printStackTrace();
}
return address;
}
//My Retrofit call
Observable<modelTwo> secondWebService(#Path("lat") double lat,#Path("lon") double lon);
Consider this:
final android.location.Address address = getAddress();
Subscription subscription = Observable.just(address) ...
This is equivalent to your code, but should also make it clear that getAddress() is evaluated before RxJava is involved and has had any chance to intervene. In other words, when you use just, the subscribeOn can only move the emission of the Address (calling onNext(address) on your Subscriber) to another thread. However, the creation of the Address - that is, your getAddress - will already have happened on the main thread when you get to this point.
The easiest way to actually move getAddress to another thread is to use defer:
Subscription subscription = Observable.defer(new
Func0<Observable<android.location.Address>>() {
#Override
public Observable<android.location.Address> call() {
return Observable.just(getAddress());
}
})
.subscribeOn(Schedulers.newThread())
.flatMap(address -> mPlatformApi.secondWebService(address.getLatitude(),address.getLongitude() )
.observeOn(AndroidSchedulers.mainThread())
.subscribe(modelTwo ->
{
//updating My ui
}, throwable -> {
//Error Handling
});
This way, the whole Func0 will be executed on newThread() - not only the just but also the getAddress.

Categories

Resources