I have an API call and I want to wrap it using Observable:
private Observable<RealmResults<Account>> getAccounts() {
final Observable<RealmResults<Account>> realmAccounts =
Observable.defer(new Func0<Observable<RealmResults<Account>>>() {
#Override
public Observable<RealmResults<Account>> call() {
return RealmObservable.results(getActivity(), new Func1<Realm, RealmResults<Account>>() {
#Override
public RealmResults<Account> call(Realm realm) {
return realm.where(Account.class).findAll();
}
});
}
});
return Observable
.create(new Observable.OnSubscribe<RealmResults<Account>>() {
#Override
public void call(final Subscriber<? super RealmResults<Account>> subscriber) {
DataBridge.getAccounts(Preferences.getString(Constant.ME_GUID, ""), new OnResponseListener() {
#Override
public void OnSuccess(Object data) {
Log.d("Stream", "onSuccess");
realmAccounts.subscribe(subscriber);
}
#Override
public void onFailure(Object data) {
subscriber.onError(new Exception(data.toString()));
}
});
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith(realmAccounts);
}
and I use it like
Observable<Accounts> accounts = getAccounts().flatMap(
new Func1<RealmResults<Account>, Observable<Account>>() {
#Override
public Observable<Account> call(RealmResults<Account> accounts) {
return Observable.from(accounts);
}
});
How can I use the accounts observable multiple times without calling the API each time. I need to process the stream of accounts and extract different sets of data out of it.
The easiest method is to use operator cache, which internally uses ReplaySubject. It cache the source observable items and then serve the results from cache.
...
Observable<<RealmResults<Account>> cachedResult = getAccounts().cache();
Observable<Accounts> accountsObservable = cachedResult.flatMap(...);
Observable<X> xObservable = cachedResult.flatMap(...);
If you would like to avoid caching results you should use Connectable Observables. Usually it only does matter for Hot Observables. Connectable observable does not begin emitting items until its Connect method is called. You can use publish operator to convert to Connectable Observable.
ConnectableObservable<<RealmResults<Account>> connectebleObservable = getAccounts().publish();
Observable<Accounts> accountsObservable = connectebleObservable .flatMap(...);
Observable<X> xObservable = connectebleObservable .flatMap(...);
//You must subscribe before connect
accountsObservable.subsribe(...);
xObservable.subscribe(...);
//start emiting data
connectebleObservable.connect();
The important catch here is that you must subscribe before connect - to avoid data loss - otherwise you must use replay operator, which is similar to cache operator, but used for connectable observable
And what about share ?
It create ConnectableObservable and exposes it as regular Observable. First subscription automatically causes connection and emission.
Share used in your case, without replay may cause data loss or multiple executions depending on timing.
for example for 2 subscribers and one item int the stream you may have fallowing cases:
2 subscriptions created before onNext - works as expected.
second subscription created after onNext but before onComplete - second subscription gets only onComplete
second subscriptinon created after onComplete - 2 executions wihtout caching
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 -> {
});
I just start learning rxJava for Android and want to implement the common use case:
request data from cache and show to the user
request data from web
server update data in storage and automatically show it to the user
Traditionally on of the best scenarios was use CursorLoader to get data from cache, run web request in the separate thread and save data to the disk via content provider, content provider automatically notify the listener and CursorLoader autoupdate UI.
In rxJava I can do it by running two different Observers as you can see in code below, but I don't find the way how to combine this two calls into the one to reach my aim. Googling shows this thread but it looks like it just get data from the cache or data from the web server, but don't do both RxJava and Cached Data
Code snippet:
#Override
public Observable<SavingsGoals> getCachedSavingsGoal() {
return observableGoal.getSavingsGoals()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
#Override
public Observable<SavingsGoals> getRecentSavingsGoal() {
return api.getSavingsGoals()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
model.getCachedSavingsGoal().subscribe(new Observer<SavingsGoals>() {
#Override
public void onCompleted() {
// no op
}
#Override
public void onError(Throwable e) {
Log.e(App.TAG, "Failed to consume cached data");
view.showError();
}
#Override
public void onNext(SavingsGoals savingsGoals) {
Log.d(App.TAG, "Show the next item");
if (savingsGoals != null && !savingsGoals.getSavingsGoals().isEmpty()) {
view.showData(savingsGoals.getSavingsGoals());
} else {
view.showError();
}
}
});
model.getRecentSavingsGoal().subscribe(new Observer<SavingsGoals>() {
#Override
public void onCompleted() {
// no op
}
#Override
public void onError(Throwable e) {
Log.e(App.TAG, "Failed to consume data from the web", e);
view.showError();
}
#Override
public void onNext(SavingsGoals savingsGoals) {
if (savingsGoals != null && !savingsGoals.getSavingsGoals().isEmpty()) {
view.showData(savingsGoals.getSavingsGoals());
} else {
view.showError();
}
}
});
Also, the one of issues with current approach is cache and web data are not garranted to be run sequently. It is possible when outdated data will come as latest and override recent from web.
To solve this issue I implemented Observer merge with filtration by timestamp: it get data from cache, pass it to the next observer and if cache is outdated fire new call to the web - case for thread competition solved by the filtration with timestamps. However, the issue with this approach I can not return cache data from this Observable - I need to wait when both requests finish their work.
Code snippet.
#Override
public Observable<Timestamped<SavingsGoals>> getSavingGoals() {
return observableGoal
.getTimestampedSavingsGoals()
.subscribeOn(Schedulers.io())
.flatMap(new Func1<Timestamped<SavingsGoals>, Observable<Timestamped<SavingsGoals>>>() {
#Override
public Observable<Timestamped<SavingsGoals>> call(Timestamped<SavingsGoals> cachedData) {
Log.d(App.FLOW, "getTimestampedSavingsGoals");
return getGoalsFromBothSources()
.filter(filterResponse(cachedData));
}
})
.subscribeOn(AndroidSchedulers.mainThread());
}
private Func1<Timestamped<SavingsGoals>, Boolean> filterResponse(Timestamped<SavingsGoals> cachedData) {
return new Func1<Timestamped<SavingsGoals>, Boolean>() {
#Override
public Boolean call(Timestamped<SavingsGoals> savingsGoals) {
return savingsGoals != null
&& cachedData != null
&& cachedData.getTimestampMillis() < savingsGoals.getTimestampMillis()
&& savingsGoals.getValue().getSavingsGoals().size() != 0;
}
};
}
private Observable<Timestamped<SavingsGoals>> getGoalsFromBothSources() {
Log.d(App.FLOW, "getGoalsFromBothSources:explicit");
return Observable.merge(
observableGoal.getTimestampedSavingsGoals().subscribeOn(Schedulers.io()),
api.getSavingsGoals()
.timestamp()
.flatMap(new Func1<Timestamped<SavingsGoals>, Observable<Timestamped<SavingsGoals>>>() {
#Override
public Observable<Timestamped<SavingsGoals>> call(Timestamped<SavingsGoals> savingsGoals) {
Log.d(App.FLOW, "getGoalsFromBothSources:implicit");
return observableGoal.saveAllWithTimestamp(savingsGoals.getTimestampMillis(), savingsGoals.getValue().getSavingsGoals());
}
}))
.subscribeOn(Schedulers.io());
}
Do you know the approach to do this in one Observer?
Potential solution:
#Override
public Observable<SavingsGoals> getSavingGoals() {
return api.getSavingsGoals()
.publish(network ->
Observable.mergeDelayError(
observableGoal.getSavingsGoals().takeUntil(network),
network.flatMap(new Func1<SavingsGoals, Observable<SavingsGoals>>() {
#Override
public Observable<SavingsGoals> call(SavingsGoals savingsGoals) {
return observableGoal.saveAll(savingsGoals.getSavingsGoals());
}
})
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
Sorry, hot replacement in IDE hide the issue which this approach has: first one in case if network unavailable and cache thread completes first, the error will terminate whole merge (solved by mergeDelayError), second one is in case when cache is empty and returns first data from web request will not be return on subscriber. As you can see my method returns Observable after save and traditional merge as I shown in my code properly handle this case but takeUntil by some reason can not. Question is still open.
For first question : You can save the result from Network Result by using doOnNext Method, It would looks something like this
public Observable<NetworkResponse> getDataFromNetwork(
final Request request) {
return networkCall.doOnNext(networkResponse -> saveToStorage(networkResponse);
}
Now to combine the two results from both Storage and Online, the best way is to combine with publish and merge. I recommend watching this talk.
The code would look something like this
public Observable<Response> getData(final Request request) {
return dataService.getDataFromNetwork(request)
.publish(networkResponse -> Observable.merge(networkResponse, dataService.getDataFromStorage(request).takeUntil(networkResponse)));
}
Why use publish and merge you my ask? publish method makes the response accessible in the callback. takeUntil means that you will take the data from storage but you will stop it IF for some reason, network call is finished before accessing storage data is finished. This way, you can be sure that new data from network is always shown even if it's finished before getting old data from storage.
The last but not least, in your subscriber OnNext just add the items to the list. (list.clear and list.addAll) Or similar functions or in you case view.showData()
EDIT: For The call getting disrupted when there's an error from network, add onErrorResumeNext at the end.
public Observable<Response> getData(final Request request) {
return dataService.getDataFromNetwork(request)
.publish(networkResponse -> Observable.merge(networkResponse, dataService.getDataFromStorage(request).takeUntil(networkResponse)))
.onErrorResumeNext(dataService.getDataFromStorage(request);
}
I'd recommend to "listen" only to local data, and refresh it when API response came.
Let say for getting local data you have something like:
#Nonnull
public Observable<SomeData> getSomeDataObservable() {
return Observable
.defer(new Func0<Observable<SomeData>>() {
#Override
public Observable<SomeData> call() {
return Observable.just(getSomeData());
}
});
}
So you need to add PublishSubject that will emit every time, when local data was updated (refreshSubject):
#Nonnull
public Observable<SomeData> getSomeDataObservableRefreshable() {
return refreshSubject.startWith((Object)null).switchMap(new Func1() {
public Observable<T> call(Object o) {
return getSomeDataObservable();
}
}
}
Now you need to subscribe only to getSomeDataObservableRefreshable(), and each time when data came from API, you update it and make refreshSubject .onNext(new Object())
Also i'd recommend to take a look to rx-java-extensions lib, it has alot of "cool tools" for RxAndroid. For example solution for your problem would be:
#Nonnull
public Observable<SomeData> getSomeDataObservable() {
return Observable
.defer(new Func0<Observable<SomeData>>() {
#Override
public Observable<SomeData> call() {
return Observable.just(getSomeData());
}
})
.compose(MoreOperators.<SomeData>refresh(refreshSubject));
}
I have this query to update data already in my realm table;
for (MyGameEntrySquad squad : response.body().getSquad()) {
subscription = realm.where(RealmPlayer.class).equalTo("id", squad.getPlayer().getId())
.findFirstAsync()
.asObservable()
.subscribe(new Action1<RealmObject>() {
#Override
public void call(RealmObject realmObject) {
}
});
}
I would like to perform this query asynchronously then display the results on the UI.
Basically, whatever is been returned by response.body().getSquad() has an id matching a record already in the DB; and that is what am using in my equalTo method.
Based on the data received, I would like to update two columns on each of the record matching the IDs.
However, I am facing a few challenges on this:
The Action1 in subscribe is returning a RealmObject instead of a PlayerObject
How to proceed from here
Any guidance on this will be appreciated.
Thanks
Update
if (response.isSuccessful()) {
//asynchronously update the existing players records with my squad i.e is_selected
for (MyGameEntrySquad squad : response.body().getSquad()) {
realm.where(RealmPlayer.class).equalTo("id", squad.getPlayer().getId())
.findFirstAsync()
.<RealmPlayer>asObservable()
.filter(realmPlayer -> realmPlayer.isLoaded())
.subscribe(player -> {
realm.beginTransaction();
if (squad.getPlayer().getPosition().equals("GK")) {
player.setPlaygroundPosition("gk");
player.setIsSelected(true);
}
// pick the flex player
if (squad.isFlex()) {
player.setPlaygroundPosition("flex");
player.setIsSelected(true);
}
// pick the Goalie
if (squad.getPlayer().getPosition().equals("GK")) {
player.setPlaygroundPosition("gk");
player.setIsSelected(true);
}
// pick the DFs
if ((squad.getPlayer().getPosition().equals("DF")) && (!squad.isFlex())) {
int dfCounter = 1;
player.setPlaygroundPosition(String.format(Locale.ENGLISH, "df%d", dfCounter));
player.setIsSelected(true);
dfCounter++;
}
// pick the MFs
if ((squad.getPlayer().getPosition().equals("MF")) && (!squad.isFlex())) {
int mfCounter = 1;
player.setPlaygroundPosition(String.format(Locale.ENGLISH, "mf%d", mfCounter));
player.setIsSelected(true);
mfCounter++;
}
// pick the FWs
if ((squad.getPlayer().getPosition().equals("FW")) && (!squad.isFlex())) {
int fwCounter = 1;
player.setPlaygroundPosition(String.format(Locale.ENGLISH, "mf%d", fwCounter));
player.setIsSelected(true);
fwCounter++;
}
realm.copyToRealmOrUpdate(player);
realm.commitTransaction();
updateFieldPlayers();
});
}
hideProgressBar();
}
realm.where(RealmPlayer.class).equalTo("id", squad.getPlayer().getId())
.findFirstAsync()
.<RealmPlayer>asObservable()
.subscribe(new Action1<RealmPlayer>() {
#Override
public void call(RealmPlayer player) {
}
});
You should do like that.
Btw, it's bad idea to do it in a cycle - check in method of RealmQuery.
for (MyGameEntrySquad squad : response.body().getSquad()) { // btw why is this not `Observable.from()`?
subscription = realm.where(RealmPlayer.class).equalTo("id", squad.getPlayer().getId())
.findFirstAsync()
.asObservable()
This should not be on the UI thread. It should be on a background thread. On a background thread, you need to use synchronous query instead of async query.
Even on the UI thread, you'd still need to filter(RealmObject::isLoaded) because it's an asynchronous query, and in case of findFirstAsync() you need to filter for RealmObject::isValid as well.
For this case, you would not need asObservable() - this method is for observing a particular item and adding a RealmChangeListener to it. Considering this should be on a background thread with synchronous query, this would not be needed (non-looper background threads cannot be observed with RealmChangeListeners).
You should also unsubscribe from any subscription you create when necessary.
And yes, to obtain RealmPlayer in asObservable(), use .<RealmPlayer>asObservable().
In short, you should put that logic on a background thread, and listen for changes on the UI thread. Background thread logic must be done with the synchronous API. You will not need findFirstAsync for this.
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?
I'm new to RxJava, here's my case,
send request A and will get List<A> back
for each A, send request AA and will get AA back, bind A and AA then
there is B & BB with similar logic
do something only after all requests complete
Example:
request(url1, callback(List<A> listA) {
for (A a : listA) {
request(url2, callback(AA aa) {
a.set(aa);
}
}
}
A and B are independent
How to structure the code? I also used Retrofit as network client.
OK, I think this should solve the first part of your problem:
Notice that the second call to flatMap is given 2 arguments - there is a version of flatMap that not only produces an Observable for each input item but that also take a second function which in turn will combine each item from the resulting Observable with the corresponding input item.
Have a look at the third graphic under this heading to get an intuitive understanding:
https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap-concatmap-and-flatmapiterable
Observable<A> obeservableOfAs = retrofitClient.getListOfAs()
.flatMap(new Func1<List<A>, Observable<A>>() {
#Override
public Observable<A> call(List<A> listOfAs) {
return Observable.from(listOfAs);
}
)}
.flatMap(new Func1<A, Observable<AA>>() {
#Override
public Observable<AA> call(A someA) {
return retrofitClient.getTheAaForMyA(someA);
}
},
new Func2<A, AA, A>() {
#Override
public A call(A someA, AA theAaforMyA) {
return someA.set(theAaforMyA);
}
})
...
From here on I am still not sure how you want to continue: Are you ready to just subscribe to the resulting Observable of As? That way you could handle each of the As (onNext) or just wait until all are done (onCompleted).
ADDENDUM: To collect all Items into a single List at the end, that is turn your Observable<A> into an Observable<List<A>> use toList().
https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tolist
So you have:
Observable<List<A>> observableOfListOfAs = observableOfAs.toList();
If you need more fine grained control over the construction of your list, you can also use reduce.
https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce
For the Bs, simply duplicate the whole flow you used for the As.
You can then use zip to wait for both flows to complete:
Observable.zip(
observableOfListOfAs,
observableOfListOfBs,
new Func2<List<A>, List<B>, MyPairOfLists>() {
#Override
public MyPairOfLists call(List<A> as, List<B> bs) {
return new MyPairOfLists(as, bs);
}
}
)
.subscribe(new Subscriber<MyPairOfLists>() {
// onError() and onCompleted() are omitted here
#Override
public void onNext(MyPairOfLists pair) {
// now both the as and the bs are ready to use:
List<A> as = pair.getAs();
List<B> bs = pair.getBs();
// do something here!
}
});
I suppose you can guess the definition of MyPairOfLists.