RxJava Chained Observables and NetworkMainThreadException - android

So I have this code:
public Observable<AbstractXMPPConnection> connect(final AbstractXMPPConnection connection) {
return Observable.<AbstractXMPPConnection>create(subscriber -> {
try {
AbstractXMPPConnection connection2 = connection.connect();
if (connection2.isConnected()) {
subscriber.onNext(connection2);
subscriber.onCompleted();
}
} catch (SmackException | IOException | XMPPException e) {
e.printStackTrace();
subscriber.onError(e);
}
})
.doOnError(throwable -> LOGI("111", "Connection OnError called"));
}
public Observable<AbstractXMPPConnection> connectWithRetry(final AbstractXMPPConnection connection) {
return connect(connection)
.retryWhen(attempts -> attempts.zipWith(Observable.range(1, MAX_CONNECTION_TRIES), (throwable, integer) -> new Pair<>(throwable, integer))
.flatMap(pair -> {
if (pair.second == MAX_LOGIN_TRIES)
return Observable.error(pair.first);
return Observable.timer(pair.second, TimeUnit.SECONDS);
}));
}
public void connect() {
assertTrue("To start a connection to the server, you must first call init() method!",
this.connectionConfig != null);
connectionHelper.connectWithRetry(connection)
.observeOn(Schedulers.newThread())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<AbstractXMPPConnection>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
LOGI(TAG, "ConnectionHelper Connection onError\n");
/**{#link LoginActivity#onConnectionFailure(OnConnectionFailureEvent)} */
MainApplication.getInstance().getBusInstance().post(new OnConnectionFailureEvent());
}
#Override
public void onNext(AbstractXMPPConnection connection) {
LOGI(TAG, "ConnectionHelper Connection onNext");
// onConnected();
}
});
}
I have some questions about chaining observables. Imagining this scenario, in which I have a connect Observable, which sometimes I use, but I use mainly the connectWithRetry() Observable.
My question is, what would happen if a added this:
.observeOn(Schedulers.newThread())
.subscribeOn(AndroidSchedulers.mainThread())
To both the connect() and connectWithRetry()? In this scenario, when I call
public void connect and specify a scheduler, the previous ones are ignored?
And why am I getting NetworkOnMainThreadException? The explicit observeOn(Schedulers.newThread()) is there, it shouldnt be giving me that error

I'll address your NetworkOnMainThread issue first.
observeOn(Schedulers.newThread()) means the output will be observed on a new thread - that is, the code in your subscriber (onComplete/Error/Next) will be run on that thread.
subscribeOn(AndroidSchedulers.mainThread() means subscription will happen on the main thread - the code in your created observable (connection.connect() etc) is what is run when subscription happens.
So simply swap the schedulers:
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
So to address your first question, they're not ignored, they're just being used incorrectly. Hopefully from this you can see what would happen if you moved similar calls in to the chain inside your methods that return observables: nothing different to what you've already done. The calls would simply be in a different place.
So where to put the scheduler selection? That's up to you. You may gain increased clarity by not having the subscribeOn call inside the methods for creating your observables:
connectionHelper.connectWithRetry(connection)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
However, if you feel like you're calling this everywhere for no reason, you can instead move the subscribeOn call inside your methods:
return connect(connection)
.retryWhen(...)
.flatMap(...)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Note that these don't have to be bundled up together like this - you could subscribeOn inside your method, but leave observeOn up to any callers that want their results on a specific thread.

Please try Schedulers.io() may be issue resolve.

Related

Why doesn't doOnNext() get called?

Neither onNext() nor onCompleted() get called for my subscriber below. I've tried implementing the subscriber via doOnNext()/doOnTerminate(). I also tried doAfterTerminate(). I've also tried explicitly defining a subscriber and neither onNext() nor onCompleted() got called for it.
According to RxJS reduce doesn't continue, it's the reduce() that's not terminating so I tried adding the take(1) but that didn't work. In the same stackoverflow question someone said the problem might be my stream never closes. Aside from take(1), maybe there is some other way I should close the stream, but I don't understand ReactiveX well enough yet.
According to Why is OnComplete not called in this code? (RxAndroid), it could be that the original stream in the series doesn't terminate. But I don't see why that would matter if I'm calling take(1) which I think is supposed to emit a termination signal.
Basically, why doesn't the following line get executed?
System.out.println("doOnNext map.size()=" + map.size());
Even though the following line of code gets executed 98 times:
map.put(e.getKey(), e.getValue());
JsonObjectObservableRequest.java
import com.android.volley.toolbox.JsonObjectRequest;
...
public class JsonObjectObservableRequest {
public JsonObjectObservableRequest(int method, String url, JSONObject request) {
jsonObjectRequest = new JsonObjectRequest(method, url, request, getResponseListener(), getResponseErrorListener());
}
private Response.Listener<JSONObject> getResponseListener() {
return new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
publishSubject.onNext(Observable.just(response));
}
};
}
private Response.ErrorListener getResponseErrorListener() {
return new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Observable<JSONObject> myError = Observable.error(error);
publishSubject.onNext(myError);
}
};
}
public JsonObjectRequest getJsonObjectRequest() {
return jsonObjectRequest;
}
public Observable<JSONObject> getObservable() {
return publishSubject.flatMap(new Func1<Observable<JSONObject>, Observable< JSONObject>>() {
#Override
public Observable<JSONObject> call(Observable<JSONObject> jsonObjectObservable) {
return jsonObjectObservable;
}
});
}
}
JsonObjectObservableRequest calling code
import com.android.volley.Request;
import com.android.volley.RequestQueue;
...
JsonObjectObservableRequest jsonObjectObservableRequest = new JsonObjectObservableRequest(Request.Method.GET, idURLString, null, keyId, key);
Observable<JSONObject> jsonObjectObservable = jsonObjectObservableRequest.getObservable();
jsonObjectObservable
.map(json -> {
try {
return NetworkAccountIdDatasource.parseIdJSON(json);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
})
.flatMapIterable(x -> x)
.map(s -> new AbstractMap.SimpleEntry<String, String>("Name of " + s, "short id for " + s.substring(4)))
.reduce(new HashMap<String, String>(), (map, e) -> {
map.put(e.getKey(), e.getValue());
return map;
})
.take(1)
.doOnNext(map -> {
System.out.println("doOnNext map.size()=" + map.size());
})
.doOnTerminate(() -> {
System.out.println("doOnTerminate");
})
.subscribe();
final RequestQueue queue = Volley.newRequestQueue(context);
queue.add(jsonObjectObservableRequest.getJsonObjectRequest());
You identified correctly the problem, you're not calling onCompleted() on the source network Observable (at the JsonObjectObservableRequest class), while the take(1) is not helpful either because you put it before the reduce.
As you were probably understood, reduce must operate on Observable with finite number of emissions, as it's emit the accumulated item when all items have been emitted and thus it's relies on the onCompleted() event to know when the observed stream has end.
I think that in your case, the best thing is to change the way JsonObjectObservableRequest operates, you don't need a Subject for this, you can use creation methods to wrap a callback (you can see my answer here, and read more about bridging between callback world to RxJava here.
Moreover, you don't need the emit an Observable of something and then flatmap it to items, you can simply emit the item with onNext() and emit an error with onError(), actually you're hiding the errors and converting them to an emission, this might make it harder to handle errors down the stream.
You should callonCompleted() at onResponse() after you call onNext() to signal the completion. and in the case of error, signaling onError will notify termination of the stream.
Another concern is canceling the request which seems not handled in this way, you can read the sources up to see how it can be done when wrapping callbacl call.
I found a way to get the doOnTerminate() and doOnNext() to be called. It was to place the take(1) statement higher up in the stream processing. I got this idea based on the comment from #yosriz . I wish I understood why this fixed the problem, but I don't because I thought a termination signal would just get propagated through, but I guess there is a lot more I need to learn about ReactiveX / reactive functional programming / etc..
JsonObjectObservableRequest calling code
import com.android.volley.Request;
import com.android.volley.RequestQueue;
...
JsonObjectObservableRequest jsonObjectObservableRequest = new JsonObjectObservableRequest(Request.Method.GET, idURLString, null, keyId, key);
Observable<JSONObject> jsonObjectObservable = jsonObjectObservableRequest.getObservable();
jsonObjectObservable
.map(json -> {
try {
return NetworkAccountIdDatasource.parseIdJSON(json);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
})
.take(1) // SOLUTION: MOVED THIS UP FROM BELOW reduce()
.flatMapIterable(x -> x)
.map(s -> new AbstractMap.SimpleEntry<String, String>("Name of " + s, "short id for " + s.substring(4)))
.reduce(new HashMap<String, String>(), (map, e) -> {
map.put(e.getKey(), e.getValue());
return map;
})
// .take(1) // SOLUTION: MOVE THIS ABOVE flatMapIterable()
.doOnNext(map -> {
System.out.println("doOnNext map.size()=" + map.size());
})
.doOnTerminate(() -> {
System.out.println("doOnTerminate");
})
.subscribe();
final RequestQueue queue = Volley.newRequestQueue(context);
queue.add(jsonObjectObservableRequest.getJsonObjectRequest());
Furthermore, it works also if I put take(1) as the very first operation, before the first map() operation (the one that calls parseIdJSON()). However, if I put take(1) as the operation just before reduce(), it only partially works; the good thing is doOnNext() gets called but the bad thing is the map.size() is always 1 when it should be 98 for my data set.

RxAndroid Subscribe code never called

I'm fairly new to RxJava and RxAndroid, and while some things work, I'm now completely stumped by what I see as basic functionality not working.
I have a subscribe call on a Subject that never seems to run, and I can't figure out why:
public class PairManager implements DiscoveryManagerListener {
private Subscription wifiAvailableSubscription;
private Subscription debugSubscription;
private DiscoveryManager discoveryManager;
private AsyncSubject<Map<String, ConnectableDevice>> availableDevices;
public PairManager(Context appContext) {
DiscoveryManager.init(appContext);
discoveryManager = DiscoveryManager.getInstance();
discoveryManager.addListener(this);
availableDevices = AsyncSubject.<Map<String, ConnectableDevice>> create();
//
// This subscription doesn't work
//
debugSubscription = availableDevices
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Map<String, ConnectableDevice>>() {
#Override
public void call(Map<String, ConnectableDevice> stringConnectableDeviceMap) {
//
// This code is never run !
//
Timber.d(">> Available devices changed %s", stringConnectableDeviceMap);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Timber.d("Subscription failed %s", throwable);
}
});
availableDevices.onNext(Collections.<String, ConnectableDevice>emptyMap());
wifiAvailableSubscription = ReactiveNetwork.observeNetworkConnectivity(appContext)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Connectivity>() {
#Override
public void call(Connectivity connectivity) {
if (connectivity.getState().equals(NetworkInfo.State.CONNECTED) && connectivity.getType() == ConnectivityManager.TYPE_WIFI) {
discoveryManager.start();
} else {
discoveryManager.stop();
availableDevices.onNext(Collections.<String, ConnectableDevice>emptyMap());
}
}
});
}
public AsyncSubject<Map<String, ConnectableDevice>> getAvailableDevices() {
return availableDevices;
}
#Override
public void onDeviceAdded(DiscoveryManager manager, ConnectableDevice device) {
Timber.d("onDeviceAdded %s", device);
availableDevices.onNext(manager.getAllDevices());
Timber.d("Sanity check %s", availableDevices.getValue());
}
// ...
}
Is there a way to debug what is going wrong? I have tried creating basic Observable.from-type calls and logging those, and that works as expected. The sanity check log in onDeviceAdded also prints and indicates that availableDevices has in fact updated as expected. What am I doing wrong?
I've found the issue, I've used AsyncSubjects which only ever emit values when they are Completed, where I expect the functionality of BehaviorSubjects.
From the doccumentation:
When Connectivity changes, subscriber will be notified. Connectivity can change its state or type.
You say:
I have a subscribe call on a Subject
A subject won't return te last value. I will only return a value when onNext is called. I assume the Connectivity never changes so it never fires.

How to execute different retrofit requests inside RxJava's switchMap() operator?

I have a searchBar (an EditText) with four tabs below it (each tab should display different results). I'm using RxJava with RxBinding to listen and react to text changes events, and I'm using switchMap() operator to execute a Retrofit service for each text change emission.
Since user can select any of the four tabs I actually execute the corresponding Retrofit request for that tab.
For each of those Retrofit services I receive a different response object.
How can I handle different return types inside switchMap() since the last one needs a common type for all?
I have already asked a similar question previously but the answer while it works doesn't lets me to consume the data from my subscriber. Or is my approach wrong from the beginning and I should try a different approach ?
Code :
RxTextView.textChangeEvents(searchbar.getEditText())
.debounce(400, TimeUnit.MILLISECONDS)
.filter(new Func1<TextViewTextChangeEvent, Boolean>() {
#Override
public Boolean call(TextViewTextChangeEvent text) {
return (text.text().length() > 2);
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(new Func1<TextViewTextChangeEvent, Observable<Void>>() {
#Override
public Observable<Void> call(TextViewTextChangeEvent textViewTextChangeEvent) {
String searchBarText = textViewTextChangeEvent.text().toString();
switch (visibleTab) {
case TAGS:
presenter.executeSearchPostsByTag(searchBarText, String.valueOf(0));
case PEOPLE:
return presenter.executeSearchPostsByPeople(searchBarText, String.valueOf(0));
case COMPANIES:
return presenter.executeSearchPostsByCompanies(searchBarText, String.valueOf(0));
case JOBS:
return presenter.executeSearchPostsByJobs(searchBarText, String.valueOf(0));
default:
return presenter.executeSearchPostsByTag(searchBarText, String.valueOf(0));
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Void>() {
#Override
public void onCompleted() {
Timber.i("ON COMPLETED");
}
#Override
public void onError(Throwable e) {
Timber.i("ON ERROR e : %s", e.getMessage());
}
#Override
public void onNext(Void aVoid) {
Timber.i("ON NEXT");
}
});
In the code above you 'll see that I have return type of Observable but that doesn't works I just added it so you 'll see what I'm doing.
Thing is, do any of the executeSearchPostsBy* methods return a non-empty Observable? If all of their Observables are empty, then you can just tack on .cast(Void.class) to all of them. If they do return non-empty observables but you don't care about the items, then tack on .ignoreElements().cast(Void.class).
If you need to do some processing for anything that is returned, then you should do that in different methods, in their own Observable chains.
If you need to do some processing that is common to all of them, then you need to adjust your model to reflect this, even if it's just wrapper classes.

PublishSubject's onNext call in different thread after I update to Retrofit 2.0

I have a following class that my coworker created while we were using Retrofit 1.9
public class SomeApiCallAction {
private Subscription subscription;
private NoInternetConnectionInterface noInternetConnectionInterface;
public interface NoInternetConnectionInterface {
PublishSubject<Integer> noInternetConnection(Throwable throwable);
}
public void execute(Subscriber subscriber, NoInternetConnectionInterface noInternetConnectionInterface) {
this.noInternetConnectionInterface = noInternetConnectionInterface;
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction);
}
public void cancel() {
if (this.subscription != null) {
this.subscription.unsubscribe();
}
}
private Func1<Observable<? extends Throwable>, Observable<?>> retryFunction = new Func1<Observable<? extends Throwable>, Observable<?>>() {
#Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable.flatMap(new Func1<Throwable, Observable<?>>() {
#Override
public Observable<?> call(final Throwable throwable) {
if (noInternetConnectionInterface!= null && (throwable instanceof IOException || throwable instanceof SocketTimeoutException)) {
return noInternetConnectionInterface.noInternetConnection(throwable);
}else{
return Observable.error(throwable);
}
}
});
}
}
SomeApiCallAction is just a simple class that wrap retrofit api call inside, the only thing special is its retry function. The retry function will check if throwable is kind of IOException or SocketTimeoutException or not, if it is, it will call the interface so that we can present retry dialog to user to ask whether they want to retry the operation or not. Our usage is similar to following snippet
public class SomeActivity implement NoInternetConnectionInterface {
#OnClick(R.id.button)
public void do(View v) {
new SomeApiCallAction().execute(
new Subscriber(),
this
)
}
#Override
public PublishSubject<Integer> noInternetConnection(final Throwable throwable) {
Log.i("Dev", Thread.currentThread() + " Error!");
final PublishSubject<Integer> subject = PublishSubject.create();
runOnUiThread(new Runnable() {
#Override
public void run() {
NoInternetDialogFragment dialog = NoInternetDialogFragment.newInstance();
dialog.setNoInternetDialogFragmentListener(new NoInternetDialogFragmentListener{
#Override
public void onUserChoice(boolean retry, NoInternetDialogFragment dialog) {
Log.i("Dev", Thread.currentThread() + " Button Click!");
if (retry) {
subject.onNext(1);
} else {
subject.onError(throwable);
}
dialog.dismiss();
}
});
dialog.show(getSupportFragmentManager(), NoInternetDialogFragment.TAG);
}
});
return subject;
}
}
When we were using Retrofit 1.9.0, this implementation was working perfectly. We test by turn on Airplane Mode and press the button to execute api call.
first execution fail and I got UnknownHostException in retry function.
so, I call the interface (Activity) to present retry dialog
I press retry button while still on Airplane mode to repeat the execution
as expected, every execution that happen after user press retry button failed to, I always get UnknownHostException in retry function.
If I keep pressing the retry button, retry dialog will appears forever until I turn off the airplane mode.
But after we update our dependencies to
'com.squareup.retrofit2:retrofit:2.0.2'
'com.squareup.retrofit2:adapter-rxjava:2.0.2'
We try again but this time the behaviour change,
first execution fail and I got UnknownHostException in retry function same as before.
so, I call the interface (Activity) to present retry dialog
I press retry button while still on Airplane mode to repeat the execution
But this time, in the retry function, instead of receiving UnknowHostException like what it was, I got NetworkOnMainThreadException instead
so the condition is not match, interface not gets call, and result as only 1 retry dialog presented to user.
Following is the log from above code
Thread[android_0,5,main] Error!
Thread[main,5,main] Button Click!
Do you have any idea what would cause this? Any suggestion, comment will be very appreciate.
Note : Following are other dependencies that we been using and might related. But they are not recently updated, been using these version since the beginning of this project.
'com.jakewharton:butterknife:8.0.1'
'io.reactivex:rxandroid:1.1.0'
'io.reactivex:rxjava:1.1.0'
'com.google.dagger:dagger-compiler:2.0'
'com.google.dagger:dagger:2.0'
'javax.annotation:jsr250-api:1.0'
More Info
I just reset my code back to the point when we were using Retrofit 1.9, I found the the print log is different
Thread[Retrofit-Idle,5,main] Error!
Thread[main,5,main] Button Click!
Not sure if this relevant to the issue or not, but clearly that in 1.9.0 I call interface in different thread compare to 2.0.0
Final Edit
After reading the answer from #JohnWowUs and follow to the link he provide I found that in Retrofit 2, network call will be synchronous by default
To resolve my issue, there are 2 ways to do this
1.) Do as #JohnWowUs suggest by specify the thread for retryFunction
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction, Schedulers.io());
2.) When create retrofit object, specify thread when create RxJavaCallAdapterFactory
retrofit = new Retrofit.Builder()
.baseUrl(AppConfig.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(getGson()))
.addCallAdapterFactory(
RxJavaCallAdapterFactory.createWithScheduler(
Schedulers.from(threadExecutor)
)
)
.build();
I think the problem is that when you resubscribe you're subscribing on the main thread as a consequence of using the default trampoline scheduler in retryWhen. Retrofit 1.9 handled the scheduling for you so using subscribeOn was pointless. The issue discussion is here. In Retrofit 2 I believe this has changed so you should try something like
this.subscription = retrofit.someService().someApiCall()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber)
.retryWhen(retryFunction, Schedulers.io());
For a quick solution this is what I do to resolve this issue
new Thread(new Runnable(){
public void run() {
choice.onNext(1);
}
}).start();
The app work as we expect again. But, I don't think this is the right way to resolve this issue so I'll keep this question open for further comment.

Android: Polling a server with Retrofit

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?

Categories

Resources