I want to chain up three network calls with RxJavaand Retrofit. The first call (retrieves the session token) has to be the first, the other two depend on this call and if the first call isn't finished before, the other two calls will result in an error.
For the other two calls, they should retrieve some information and update the UI. What would be the best way to proceed?
I first thought about using the zip Operator, but I'm not sure if it respects the order of the requests and as it returns a value, it felt like abusing it to just use it to bundle up the requests without any further processing.
My second approach would be to flatmap the requests and use doOnNext to update the UI once, but I'm not certain if this is the correct way.
private void setUpInitialUIState() {
restClient.requestSessionToken()
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
.flatMap(new Func1<SessionTokenResponse, Observable<CurrentPlmnResponse>>() {
#Override
public Observable<CurrentPlmnResponse> call(SessionTokenResponse sessionTokenResponse) {
return restClient.requestCurrentPlmn();
}
})
.doOnNext(new Action1<CurrentPlmnResponse>() {
#Override
public void call(CurrentPlmnResponse currentPlmnResponse) {
if (!currentPlmnResponse.isError()) {
tvProvider.setText(currentPlmnResponse.getData().getFullName());
}
}
})
.flatMap(new Func1<CurrentPlmnResponse, Observable<MonitoringStatusResponse>>() {
#Override
public Observable<MonitoringStatusResponse> call(CurrentPlmnResponse currentPlmnResponse) {
return restClient.requestMonitoringStatus();
}
})
.subscribe(new Subscriber<MonitoringStatusResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
Log.d("onError", throwable.toString());
}
#Override
public void onNext(MonitoringStatusResponse monitoringStatusResponse) {
if (monitoringStatusResponse != null && !monitoringStatusResponse.isError() && monitoringStatusResponse.getData().getSignalIcon() >= 0 && monitoringStatusResponse.getData().getSignalIcon() <= 5) {
ivSignalStrength.setImageResource(getResources().getIdentifier("ic_signal_" + monitoringStatusResponse.getData().getSignalIcon(), "drawable", getPackageName()));
tvNetworkType.setText(getNetworkTypeTitle(monitoringStatusResponse.getData().getCurrentNetworkType()));
}
}
});
}
Depends if you want your 2nd and 3rd calls to be executed in parallel or one after another. If in parallel go for the .zip and don't feel bad about it :)
3 tips on your (current) code (maybe you are aware already or slightly different in your app, so apologies):
Catch the subscription returned from the .subscribe and kill (unsubscribe) at onDestroy the latest. If the app closes the network calls will continue to live.
If .requestCurrentPlmn() is in a thread then the .setText will complain from a touching view from not ui thread exception.
You miss a .onError in your .subscribe. If a request fails, the app will crash.
Related
I am using retrofit and Rxjava to handle api calls for my mvvm android application. Based on some tutorial, i am currently using RxJava like this.
ViewModel.java
CompositeDisposable disposable = new CompositeDisposable();
private void fetchTodolist(){
loading.setValue(true);
disposable.add(
service.getToDoList("A1833")
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ApiResponse<ArrayList<TodoItem>>>() {
#Override
public void onSuccess(ApiResponse<ArrayList<TodoItem>> value) {
if(value.getStatus() == 200){
//on call success code
} else {
//on call rejected code
}
}
#Override
public void onError(Throwable e) {
// on call error code
}
})
);
}
And now i want to cache the result of the api call on successful call into room database. So i need to use another async method and tried to reuse the new thread i created before. And here's the code.
private void fetchTodolist(){
loading.setValue(true);
Scheduler a = Schedulers.newThread();
disposable.add(
service.getToDoList("A1833")
.subscribeOn(a)
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ApiResponse<ArrayList<TodoItem>>>() {
#Override
public void onSuccess(ApiResponse<ArrayList<TodoItem>> value) {
if(value.getStatus() == 200){
a.scheduleDirect(new Runnable() {
#Override
public void run() {
long inserted = dao.insert(value);
}
});
} else {
//on call rejected code
}
}
#Override
public void onError(Throwable e) {
// on call error code
}
})
);
}
I wonder if it is a bad practice and will lead to a serious problem. And if so, what's the alternative.
Schedulers uses cached references thus newThread() returns the same Scheduler instance.
Schedulers.newThread() == Schedulers.newThread()
Generally you should avoid using newThread because it creates a new thread for every application of the operator. So if you run the sequence multiple times, new worker threads are created and dismissed without any kind of reuse. This is especially true for newThread().scheduleDirect which will start a new thread just for that single runnable and stop it afterwards.
It is recommended you use Schedulers.io() for IO operations so that those underlying worker threads are reused as much as possible later.
I'm learning RxJava2 and I need to chain three observables:
The first one performs operations on the data:
Completable performOperations(Data data); // performs expensive operations.
The second one uploads data to a server1 and emits percentage progress.
Observable<Integer> uploadToServer1(Data data); // while it performs the upload, it calls several times onNext(progress) and finally calls onComplete().
The third one just informs to a server2 that the upload was done.
Completable informUploadedToServer2(Data data); // just calls a REST API.
I would like to show the progress in my Activity of the second observable and finally show success when the third one finishes successfully. If any of the three observables throws an exception I should show the error in the Activity as well.
I've tried to use concat to chain but it won't compile because uploadToServer1 emits the Integer type and the rest doesn't.
public void upload(Data data, MyCallback callback) {
Observable.concat(performOperations(data).toObservable(), uploadToServer1(data), informUploadedToServer2(data))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<Integer>() {
#Override
public void onNext(Integer integer) {
callback.onProgressChanged(integer);
}
#Override
public void onError(Throwable e) {
callback.onError();
}
#Override
public void onComplete() {
callback.onSuccess();
}
});
}
I've seen that if I change to
Observable.concat(performOperations(data).<Integer>toObservable(), uploadToServer1(data), informUploadedToServer2(data).<Integer>toObservable())
it will work, however, is this the recommended approach?
What is more, what if the first observable emits non-Integers?, for example, a DataDiff object which would describe the modification after a certain operation was performed:
Observable<DataDiff> performOperations(Data data);
How should I subscribe so that I can listen for onNext(Integer) and also onNext(DataDiff) so that the Activity can update the view accordingly?
Thanks.
I would do that in a different way, a more "streamy" approach.
First performOperations(), then use andThen operator to concatenate with the Observable<Integer>, and then you can use concatWith so that after that all the elements from the Observable<Integer> are emitted informUploadedToServer2 is executed. You can then handle the Integer emitted in the subscription consumer, if you observeOn(AndroidSchedulers.mainThread) you can than safely notify your Activity there
performOperations(data)
.andThen(uploadToServer1(data))
.concatWith(informUploadedToServer2(data))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Integer>() {
#Override
public void accept(Integer integer) throws Exception {
// notify your Activity here
}
});
In case you needed to intercept the completion of one of the streams, you could use doOnComplete, for instance
performOperations(data)
.doOnComplete(new Action() {
#Override
public void run() throws Exception {
// after performOperations has completed but before
// uploadToServer1 has started
}
})
.andThen(uploadToServer1(data))
// ...
In case performOperations() returned an Observable<DataDiff>, you could use doOnNext to intercept all the events, and then use ignoreElements operator to convert it to a Completable and then use andThen as before
performOperations()
.doOnNext(new Consumer<DataDiff>() {
#Override
public void accept(DataDiff dataDiff) throws Exception {
// handle DataDiff here
}
})
.ignoreElements()
.andThen(uploadToServer1())
// ...
I wrote a method to print the output from flatMap (Pseudo code):
Observable.just(...).repeat()
.flatMap( return Observable.just([double]))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Double>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
tvConfidence.setText(e.getMessage());
}
#Override
public void onNext(Double aDouble) {
tvConfidence.setText("Confidence :" + aDouble);
}
});
When I run these code, it works a few seconds but after a few seconds, it would not run onto the onNext method again. I don't know why, because I debug the code, it will run the Observable.just(double), and the value always changed but it would not execute the code setText to refresh the textView.
My guess is that due to that particular flatMap overload, you eventually start to accumulate a lot of just because flatMap is unbounded-in. Try with flatMap(f, 1) to limit the concurrency level.
I have an Observable and subscribe to it. I need to not miss any emitted result, so I use onBackpressureBuffer like following:
Observable<Data> observable = observable.onBackpressureBuffer();
if (BuildConfig.DEBUG)
{
observable
.subscribeOn(HandlerScheduler.from(dataManager.getBackgroundHandler()))
.observeOn(HandlerScheduler.from(dataManager.getBackgroundHandler()))
.subscribe(new MeasuringSubscriber(...));
}
// Here is the real observer that I need in my app
observable
.subscribeOn(HandlerScheduler.from(dataManager.getBackgroundHandler()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Data>()
{
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Data data) {
}
});
The MeasuringSubscriber is a custom subscriber that just logs how long a task needs, that's all.
Problem
If I add the MeasuringSubscriber, the subscribers do not work anymore and never emit a result. Why? And how can I make that working?
EDIT - NEW PROBLEM
Currently it's working, but the MeasuringSubscriber is somehow blocking, meaning, first all items are emitted one by one to the MeasuringSubscriber and only afterwards all items are emitted one by one to the main subscriber... Any ideas what could cause that?
I have a solution for that - I can extend my main observalbe from the MeasuringObservable - but I rather would like to know why this happens and how to avoid this...
I tried using publish + connect, but still it does emit all items to the first subscriber before emitting them to the second one...
Here is a use case I am trying to resolve with rxJava and Dagger2 in my android app.
Load recording details
Check backend server if HLS transcode exists (REST Call)
If exists, monitor until process is 100% (REST Call every n seconds until 100%)
If does not exist, don't call monitor process
The REST Calls are injected through a dagger component. I am struggling with setting up rxJava to create a monitor that will refresh the REST Call until the process is 100% and stops, or the user just backs out the screen.
I am not sure I am asking this question in the correct way, so if an update is required, please let me know.
Here is a link to my presenter on github repo. This loads the data and needs to trigger the updates back to the fragment that is responsible for displaying data.
UPDATE: 2015-10-26 PM
I know this is probably a hack, but this is how I implemented the repeating delayed calls:
#Override
protected Observable buildUseCaseObservable() {
Action1<List<LiveStreamInfo>> onNextAction = new Action1<List<LiveStreamInfo>>() {
#Override
public void call( List<LiveStreamInfo> liveStreamInfos ) {
try {
Thread.sleep( 5000 );
} catch( InterruptedException e ) { }
}
};
return this.contentRepository.liveStreamInfos( this.filename )
.repeat( Schedulers.io() )
.doOnNext( onNextAction );
}
Then, in the call method that establishes a subsriber:
private void getProgramDetails() {
this.getProgramDetailsUseCase.execute(new ProgramDetailsSubscriber());
}
And the subscriber:
private final class LiveStreamInfosListSubscriber extends DefaultSubscriber<List<LiveStreamInfo>> {
#Override
public void onCompleted() {
...
}
#Override
public void onError( Throwable e ) {
...
}
#Override
public void onNext( List<LiveStreamInfo> liveStreamInfos ) {
if( null != liveStreamInfos && !liveStreamInfos.isEmpty() ) {
ProgramDetailsPresenter.this.showLiveStreamDetailsInView( liveStreamInfos.get( 0 ) );
if( liveStreamInfos.get( 0 ).getPercentComplete() == 100 ) {
ProgramDetailsPresenter.this.getLiveStreamsListUseCase.unsubscribe();
}
}
}
}
The subscriber will unsubscribe from the observable once the percent complete reaches 100%, cancelling all future call. The benefit here is that this subscriber fires when a user initiates the transcode, creating the live stream, from within the app, or it picks it up from the backend is it is initiated from the backend web interface.
How about adding .retry() with how often you want to retry and a large value for the number of retries to your rx observer. Then just unsubscribe from your source observable when exiting your fragment to stop the polling.