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.
Related
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.
I'm trying RxJava and I've found a small problem that I don't know how to handle.
I'm using in my application google maps API, so when the fragment with maps is created I have to call
mGoogleMapFragment.getMapAsync(OnMapReadyCallback)
this will call a callback method when map is ready to be used. Looks like a great place to use RxJava to signal that the map is ready and I can do some work on it. So I've started with something like this:
mMapReadySignal = Single.<GoogleMap>fromEmitter(objectEmitter -> {
OnMapReadyCallback callback = googleMap -> {
mMap = googleMap;
objectEmitter.onSuccess(googleMap);
};
getMapAsync(callback);
}).cache();
Cache is called to forbid multiple subscriptions to happen - I just want to subscribe once.
Then I'm using this Single in two other observables:
mMapReadySignal
.observeOn(AndroidSchedulers.mainThread())
.subscribe((googleMap) -> {
LatLng place = new LatLng(52,0, 20.0);
mMap.moveCamera(CameraUpdateFactory.zoomTo(13.0f));
mMap.moveCamera(CameraUpdateFactory.newLatLng(place));
}));
and
MyRetrofitInterface.getMarkedPlaces()
.zipWith(mMapReadySignal.toObservable(), (markedPlaces, ignore) -> markedPlaces)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
markedPlaces -> showOnMap(markedPlaces),
Throwable::printStackTrace));
Looks nice, but can look even better - I don't use anywhere object returned by mMapReadySignal. So why don't make it a Completable?
mMapReadySignal = Completable.fromEmitter(objectEmitter -> {
OnMapReadyCallback callback = googleMap -> {
mMap = googleMap;
objectEmitter.onCompleted();
};
getMapAsync(callback);
});
And here's the problem. I cannot call cache on a completable object, so what I've ended up with is that mMapReadySignal is subscribed twice. That's definately not what I want! How can I turn this Completable to be cached? Is there a way?
Oh and yes - I've been thinking about moving that "moveCamera observable" to the body of completable's emitter. However this is not a solution to my problem, as this would create an oportunity for a bug - if I'd remove all subscribers to mMapReadySignal, noone will subscribe to it, therefore I'd end up with a googleMap without camera moved to my desired position;
as Completable doesn't emit nothing (no onNext) there is no sense of Cache to it (no events to cache).
However, you can achieve this by transferring the Completeable to Observable using Completable.toObservable() method, than cache this Observable using Observable.cache(), than transfer it again to Completable.
you can also just create Observable with the operation, cache it, and then call transfer it to completable using toCompletable()
You shouldn't need to cache a completable.
final Completable c = Completable.fromAction(new Action0() {
#Override
public void call() {
}
});
c.observeOn(Schedulers.computation()).subscribe(new Action0() {
#Override
public void call() {
Log.d("X", "A");
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
c.observeOn(Schedulers.io()).subscribe(new Action0() {
#Override
public void call() {
Log.d("X", "B");
}
});
prints out 'A', then two seconds later, 'B'.
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
});
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.
I'm building a 2 Player game on Android. The game works turnwise, so player 1 waits until player 2 made his input and vice versa. I have a webserver where I run an API with the Slim Framework. On the clients I use Retrofit. So on the clients I would like to poll my webserver (I know it's not the best approach) every X seconds to check whether there was an input from player 2 or not, if yes change UI (the gameboard).
Dealing with Retrofit I came across RxJava. My problem is to figure out whether I need to use RxJava or not? If yes, are there any really simple examples for polling with retrofit? (Since I send only a couple of key/value pairs) And if not how to do it with retrofit instead?
I found this thread here but it didn't help me too because I still don't know if I need Retrofit + RxJava at all, are there maybe easier ways?
Let's say the interface you defined for Retrofit contains a method like this:
public Observable<GameState> loadGameState(#Query("id") String gameId);
Retrofit methods can be defined in one of three ways:
1.) a simple synchronous one:
public GameState loadGameState(#Query("id") String gameId);
2.) one that take a Callback for asynchronous handling:
public void loadGameState(#Query("id") String gameId, Callback<GameState> callback);
3.) and the one that returns an rxjava Observable, see above. I think if you are going to use Retrofit in conjunction with rxjava it makes the most sense to use this version.
That way you could just use the Observable for a single request directly like this:
mApiService.loadGameState(mGameId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GameState>() {
#Override
public void onNext(GameState gameState) {
// use the current game state here
}
// onError and onCompleted are also here
});
If you want to repeatedly poll the server using you can provide the "pulse" using versions of timer() or interval():
Observable.timer(0, 2000, TimeUnit.MILLISECONDS)
.flatMap(mApiService.loadGameState(mGameId))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GameState>() {
#Override
public void onNext(GameState gameState) {
// use the current game state here
}
// onError and onCompleted are also here
}).
It is important to note that I am using flatMap here instead of map - that's because the return value of loadGameState(mGameId) is itself an Observable.
But the version you are using in your update should work too:
Observable.interval(2, TimeUnit.SECONDS, Schedulers.io())
.map(tick -> Api.ReceiveGameTurn())
.doOnError(err -> Log.e("Polling", "Error retrieving messages" + err))
.retry()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(sub);
That is, if ReceiveGameTurn() is defined synchronously like my 1.) above, you would use map instead of flatMap.
In both cases the onNext of your Subscriber would be called every two seconds with the latest game state from the server. You can process them one after another of limit the emission to a single item by inserting take(1) before subscribe().
However, regarding the first version: A single network error would be first delivered to onError and then the Observable would stop emitting any more items, rendering your Subscriber useless and without input (remember, onError can only be called once). To work around this you could use any of the onError* methods of rxjava to "redirect" the failure to onNext.
For example:
Observable.timer(0, 2000, TimeUnit.MILLISECONDS)
.flatMap(new Func1<Long, Observable<GameState>>(){
#Override
public Observable<GameState> call(Long tick) {
return mApiService.loadGameState(mGameId)
.doOnError(err -> Log.e("Polling", "Error retrieving messages" + err))
.onErrorResumeNext(new Func1<Throwable, Observable<GameState>(){
#Override
public Observable<GameState> call(Throwable throwable) {
return Observable.emtpy());
}
});
}
})
.filter(/* check if it is a valid new game state */)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GameState>() {
#Override
public void onNext(GameState gameState) {
// use the current game state here
}
// onError and onCompleted are also here
}).
This will every two seconds:
* use Retrofit to get the current game state from the server
* filter out invalid ones
* take the first valid one
* and the unsubscribe
In case of an error:
* it will print an error message in doOnNext
* and otherwise ignore the error: onErrorResumeNext will "consume" the onError-Event (i.e. your Subscriber's onError will not be called) and replaces it with nothing (Observable.empty()).
And, regarding the second version: In case of a network error retry would resubscribe to the interval immediately - and since interval emits the first Integer immediately upon subscription the next request would be sent immediately, too - and not after 3 seconds as you probably want...
Final note: Also, if your game state is quite large, you could also first just poll the server to ask whether a new state is available and only in case of a positive answer reload the new game state.
If you need more elaborate examples, please ask.
UPDATE: I've rewritten parts of this post and added more information in between.
UPDATE 2: I've added a full example of error handling with onErrorResumeNext.
Thank you, I finally made it in a similar way based the post I referred to in my question. Here's my code for now:
Subscriber sub = new Subscriber<Long>() {
#Override
public void onNext(Long _EmittedNumber)
{
GameTurn Turn = Api.ReceiveGameTurn(mGameInfo.GetGameID(), mGameInfo.GetPlayerOneID());
Log.d("Polling", "onNext: GameID - " + Turn.GetGameID());
}
#Override
public void onCompleted() {
Log.d("Polling", "Completed!");
}
#Override
public void onError(Throwable e) {
Log.d("Polling", "Error: " + e);
}
};
Observable.interval(3, TimeUnit.SECONDS, Schedulers.io())
// .map(tick -> Api.ReceiveGameTurn())
// .doOnError(err -> Log.e("Polling", "Error retrieving messages" + err))
.retry()
.subscribe(sub);
The problem now is that I need to terminate emitting when I get a positive answer (a GameTurn). I read about the takeUntil method where I would need to pass another Observable which would emit something once which would trigger the termination of my polling. But I'm not sure how to implement this.
According to your solution, your API method returns an Observable like it is shown on the Retrofit website. Maybe this is the solution? So how would it work?
UPDATE:
I considered #david.miholas advices and tried his suggestion with retry and filter. Below you can find the code for the game initialization. The polling should work identically: Player1 starts a new game -> polls for opponent, Player2 joins the game -> server sends to Player1 opponent's ID -> polling terminated.
Subscriber sub = new Subscriber<String>() {
#Override
public void onNext(String _SearchOpponentResult) {}
#Override
public void onCompleted() {
Log.d("Polling", "Completed!");
}
#Override
public void onError(Throwable e) {
Log.d("Polling", "Error: " + e);
}
};
Observable.interval(3, TimeUnit.SECONDS, Schedulers.io())
.map(tick -> mApiService.SearchForOpponent(mGameInfo.GetGameID()))
.doOnError(err -> Log.e("Polling", "Error retrieving messages: " + err))
.retry()
.filter(new Func1<String, Boolean>()
{
#Override
public Boolean call(String _SearchOpponentResult)
{
Boolean OpponentExists;
if (_SearchOpponentResult != "0")
{
Log.e("Polling", "Filter " + _SearchOpponentResult);
OpponentExists = true;
}
else
{
OpponentExists = false;
}
return OpponentExists;
}
})
.take(1)
.subscribe(sub);
The emission is correct, however I get this log message on every emit:
E/Pollingļ¹ Error retrieving messages: java.lang.NullPointerException
Apperently doOnError is triggered on every emit. Normally I would get some Retrofit debug logs on every emit which means that mApiService.SearchForOpponent won't get called. What do I do wrong?