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'.
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 want to perform the following. I have a list of transactions which i want to update by making 2 api requests (i am using retrofit2), for each transactions and then saving the result into a database(using the observer). After some searching i decided to use zip operator to combine the 2 requests but the issue that i'm have is that i cannot identify when the whole process is finished to update the UI. Code looks like this.
for (Transaction realmTransaction : allTransactions) {
Observable<Map<String, String>> obs1 = getObs1(realmTransaction);
Observable<Map<String, String>> obs2= getObs2(realmTransaction);
Observable.zip(obs1, obs2,
(map1, map2) -> {
Map<String, String> combined = new HashMap<>();
// do some processing and return a single map after
return combined;
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getObserver(realmTransaction));
}
public Observer<Map<String, String>> getObserver(Transaction t){
return new Observer<Map<String, String>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Map<String, String> stringStringMap) {
// update database
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
}
}
The observer that i have updates a field of the realmTransaction object.
My question is how do i get notified when the for loop has ended??
I would like to sent an event (maybe use EventBust) after the whole process has finish to kick off some other method.
Thanks
Also another small question that i have is about the function that i provide inside the zip operator, how can i specify on which thread that function will run on? I would like to use a computation thread for that thats why i put observeOn twice, but I couldnt find an answer anywhere
Whenever you have a for loop, you should think about range, fromArray or fromIterable. In addition, you may not need the full subscribe but doOnNext():
Observable.fromIterable(allTransactions)
.flatMap(realmTransaction -> {
Observable<Map<String, String>> obs1 = getObs1(realmTransaction);
Observable<Map<String, String>> obs2= getObs2(realmTransaction);
return Observable.zip(obs1, obs2, (map1, map2) -> {
Map<String, String> combined = new HashMap<>();
// do some processing and return a single map after
return combined;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(stringStringMap -> handle(stringStringMap, realmTransaction));
})
.ignoreElements()
.subscribe(() -> handleCompleted(), e -> handleError(e));
Since every thing is being done asynchronously , you can create a local variable outside the loop,say count and and keep incrementing it getObserver function and later check the count is equal to the length of allTransactions.
And about your second question, I think the subscribe thread will be used. You using the 2 subscribeOn, only last one will be used.
I am new at RxJava and I have some pain to execute my first 'difficult' query.
I have two Observables generated from Retrofit, one that 'ping' a new api, the other the old one. The first one will query 'http://myurl.com/newapi/ping', the second one 'http://myurl.com/oldapi/ping'. Result from this request doesn't matter, I just want to know if the server is using the new or old api.
So I would like to call both observables at the same time, and finally have a boolean at the end to know if I'm using old or new api.
I tried something like that
Observable.mergeDelayError(obsOldApi,obsNewApi)
.observeOn(AndroidSchedulers.mainThread(), true)
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String s) {
}
});
But onError will be called once (I would like it to be called only if both request failed) and when onNext is called, I don't know from which request it came (old or new api ?).
Thank you for you help
For simplicity, let say that you'll received "NEW" or "OLD" regarding which api is available.
The difficulty of your operation is to manage errors : RxJava deals errors as terminal state. So you'll have to ignore this error, using .onErrorResumeNext() for example.
Observable<String> theOld = oldApi.map(r -> "OLD")
// ignore errors
.onErrorResumeNext(Obervable.empty());
Observable<String> theNew = newApi.map(r -> "NEW")
.onErrorResumeNext(Obervable.empty());
Observable.merge(theOld, theNew)
.first() // if both api are in errors
.subscribe(api -> System.out.println("Available API : "+api));
I added the operator first : it will take only the first result ("OLD" or "NEW") but trigger an error if the previous Observable is empty, which is the case if both API are unavaible.
I 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
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?