Can anyone tell what the request() method in a DisposableSubscriber does and when it is used? We use it only when you create your own Flowable with Flowable.create? The official documentation says
request(long n):
Requests the specified amount from the upstream if its Subscription is set via onSubscribe already.
But I'm not understanding what does it mean. To try out I made a sample as below
private Flowable<Long> streamOfNums() {
return Flowable.create(e -> {
for (int i = 0; i < 500; i++) {
e.onNext((long) i);
Log.d(TAG, "produced "+i);
}
}, BackpressureStrategy.BUFFER);
}
Consumes it like
streamOfNums()
.subscribeOn(Schedulers.io())
.subscribeWith(new DisposableSubscriber<Long>() {
#Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart: ");
}
#Override
public void onNext(Long aLong) {
Log.d(TAG, "onNext: ");
try {
Log.d(TAG, "consuming data :"+aLong);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
request(4);
}
#Override
public void onError(Throwable t) {
}
#Override
public void onComplete() {
}
});
And what I can see is each time the emitter produces numbers after the delay (2000 ms) given. I have given request(4) but even without that it works in the exact same way.
can anybody explain when what request does and when it is used. Can it be used for pagination scenarios?
request allows the consumer to tell the producer how many elements to produce. By default, DisposableSubscriber requests Long.MAX_VALUE in its onStart() method in which case further request() calls have no effect.
There is rarely the need to actually call request in such end-consumers, but otherwise you could use it to avoid buffer overflow when your end-consumer acts as an asynchronous boundary:
ExecutorService executor = Executors.newSingleThreadedExecutor();
Flowable.range(1, 500)
.doOnNext(v -> Log.d("produced: " + v))
.subscribeOn(Schedulers.io())
.subscribe(new DisposableSubscriber<Long>() {
#Override protected void onStart() {
Log.d(TAG, "onStart: "); // <----- no super.onStart() here!
request(1);
}
#Override public void onNext(Long aLong) {
executor.execute(() -> {
Log.d(TAG, "onNext: ");
try {
Log.d(TAG, "consuming data :"+aLong);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
request(1);
});
}
#Override public void onError(Throwable t) {
executor.execute(() -> t.printStackTrace());
}
#Override public void onComplete() {
executor.execute(() -> Log.d("onComplete"));
}
});
Thread.sleep(100_000);
executor.shutdown();
Related
I need to send a list of objects to my remote server. As they may be numerous and big I use a flowable to send them one by one from an arraylist using request(1).
For each object a retrofit call is made to the server and in return I get the remote ID and I update the local object with the remote id.
I need to detect the end of this task : ie the last response for the last object sent to prevent multiple concurent calls for the same objects.
For the moment all works well but I get the "completed" message before the answer arrives from the remote server so before the object is updated.
How can I do this ?
Flowable<Integer> observable = Flowable.range(0, objList.size());
observable.subscribe(new DefaultSubscriber<Integer>() {
#Override
public void onStart() {
Log.d(TAG, "on start");
request(1);
}
#Override
public void onNext(Integer t) {
Log.d(TAG, "on next : " + t);
MyObj = objList.get(t);
RetrofitHelper.createService(ObjService.class, true, authType, authToken).createOrUpdateObj(objList.get(t)).flatMap(p -> {
Log.d(TAG, "recu p");
if (p != null) {
try {
p.setSyncho(true);
// save remote id on obj
ObjDB.updateObj(p);
request(1);
return Observable.empty();
} catch (Throwable th) {
ExceptionHandler.logException(th);
return Observable.error(th);
}
} else {
request(1);
return Observable.empty(); // provisoirement si pb on renvoie vide
}
})
.onErrorResumeNext(r -> {
request(1);
Observable.empty();
})
.onExceptionResumeNext(error -> Observable.empty()) // go to next on error
.subscribeOn(Schedulers.io()).onErrorReturn(error -> {
Log.d("ERROR", error.getMessage());
return 0;
})
.onErrorResumeNext(Observable.empty()).subscribe();
}
#Override
public void onError(Throwable t) {
Log.e("XXX ERROR ", "" + t);
request(1);
patientSynchroInProgress = Boolean.FALSE;
}
#Override
public void onComplete() {
Log.e("XXX COMPLETE", "complete");
}
});
You should move your retrofit call inside a map(...) operator:
Flowable<Integer> observable = Flowable.range(0, objList.size());
observable
.map(t -> {
MyObj = objList.get(t);
return RetrofitHelper.createService(ObjService.class, true, authType, authToken).createOrUpdateObj(objList.get(t)).flatMap(p -> {
Log.d(TAG, "recu p");
if (p != null) {
try {
p.setSyncho(true);
// save remote id on obj
ObjDB.updateObj(p);
return Observable.empty();
} catch (Throwable th) {
ExceptionHandler.logException(th);
return Observable.error(th);
}
} else {
return Observable.empty(); // provisoirement si pb on renvoie vide
}
})
.onErrorResumeNext(r -> {
Observable.empty();
})
.onExceptionResumeNext(error -> Observable.empty()) // go to next on error
.subscribeOn(Schedulers.io()).onErrorReturn(error -> {
Log.d("ERROR", error.getMessage());
return 0;
})
.onErrorResumeNext(Observable.empty())
})
.subscribe(new DefaultSubscriber<Integer>() {
#Override
public void onStart() {
Log.d(TAG, "on start");
}
#Override
public void onNext(Integer t) {
Log.d(TAG, "on next : " + t);
}
#Override
public void onError(Throwable t) {
Log.e("XXX ERROR ", "" + t);
patientSynchroInProgress = Boolean.FALSE;
}
#Override
public void onComplete() {
Log.e("XXX COMPLETE", "complete");
}
});
You are performing your retrofit call in onNext(...) so your network responses may not be sequential. By transforming your observable with the map(...) operator, each emission will become a separate network call. This allows your onNext(...) function to print sequential results from your retrofit calls and your onComplete() to execute when all subsequent calls are completed.
Calling subscribe from within an onNext or map is generally the wrong thing to do and indicates you should be using flatMap or concatMap in the upstream. In this case, concatMap can be used as it will only run one inner source, your retrofit call, and only do the next if this one completed.
Flowable.fromIterable(objList)
.concatMap(item ->
RetrofitHelper.createService(ObjService.class, true, authType, authToken)
.createOrUpdateObj(item)
.flatMap(p -> {
if (p != null) {
try {
p.setSyncho(true);
// save remote id on obj
ObjDB.updateObj(p);
return Observable.just(item);
} catch (Throwable th) {
ExceptionHandler.logException(th);
return Observable.<Integer>error(th);
}
} else {
return Observable.<Integer>empty(); // provisoirement si pb on renvoie vide
}
})
.onErrorResumeNext(Observable.<Integer>empty())
.toFlowable(BackpressureStrategy.BUFFER)
)
.subscribe(new DefaultSubscriber<Integer>() {
#Override
public void onStart() {
Log.d(TAG, "on start");
}
#Override
public void onNext(Integer t) {
Log.d(TAG, "on next : " + t);
}
#Override
public void onError(Throwable t) {
Log.e("XXX ERROR ", "" + t);
patientSynchroInProgress = Boolean.FALSE;
}
#Override
public void onComplete() {
Log.e("XXX COMPLETE", "complete");
}
});
Finally, I was able to get it work
Flowable.fromIterable(patientList)
.concatMap(item -> {
item.setSomething();
return RetrofitHelper.createService(ObjService.class, true, authType, authToken)
.createOrUpdateObj(item)
.flatMap(p -> {
if (p != null) {
try {
p.setSyncho(true);
// save remote id on obj
ObjDB.updateObj(p);
return Observable.empty();
} catch (Throwable th) {
ExceptionHandler.logException(th);
return Observable.error(th);
}
} else {
return Observable.empty(); // provisoirement si pb on renvoie vide
}
})
.onErrorResumeNext(Observable.empty())
.toFlowable(BackpressureStrategy.BUFFER);
}
)
.doOnNext(s -> {
Log.d(TAG, ((Obj) s).toString());
})
.doOnComplete(() -> {
// do something when completed
Log.d(TAG, "COMPLETE");
})
.subscribe();
}
}
Thank you for your help
Currently, I am fetching data from Web API using RxAndroid and Retrofit, and want to store that Data in Room database but getting an exception
As I search and found that, room database operations won't work on UI thread so I added .subscribeOn(Schedulers.io()) in RXAndroid
still it is throwing
java.lang.IllegalStateException: Cannot access the database on the main thread since it may potentially lock the UI for a long period of time.
public void onClickLogin(View view) {
io.reactivex.Observable
.zip(getLogin(Constants.EMAILID, Constants.PASSWORD),
getUserInfo(Constants.EMAILID, Constants.PASSWORD),
getProductDetails(Constants.EMAILID, Constants.PASSWORD).subscribeOn(Schedulers.io()),
.observeOn(AndroidSchedulers.mainThread())
new Function3<List<LoginModule>,
List<UserInfoModule>, ProductModule, AllZipData>() {
#Override
public AllZipData apply(List<LoginModule> loginModuleList, List<UserInfoModule> useerInfoModules, ProductModule productModule) throws Exception {
AllZipData allZipData = new AllZipData();
allZipData.setLoginModuleList(loginModuleList);
allZipData.setUserInfoModuleList(UserInfoModule);
allZipData.setProductModule(productModule);
return allZipData;
}
}).subscribe(new Observer<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
MyDatabase MyDatabase = MyDatabase.getInstance(context);
for (int i = 0; i < allZipData.getUserInfoModuleList().size(); i++) {
UserInfoTable userInfoTable = new UserInfoTable();
userInfoTable.setValue1(allZipData.getUserInfoModuleList().get(i).getValue1());
userDatabase.userDao().insertUserInfo(userInfoTable);
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: all zip data " + e.toString());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete: all data zipped");
}
});
}
how to solve this exception using RxAndroid.
How to add retryWhen();?
Where does this exception happen? If it is in onNext, that's because you specified observeOn(mainThread()) thus the database access happens on the main thread.
Try this
Observable.zip(
getLogin(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()), // <--------------------------------
getUserInfo(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()), // <--------------------------------
getProductDetails(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()) // <--------------------------------
)
.observeOn(Schedulers.io()) // <--------------------------------
.doOnNext(allZipData -> {
MyDatabase MyDatabase = MyDatabase.getInstance(context);
for (int i = 0; i < allZipData.getUserInfoModuleList().size(); i++) {
UserInfoTable userInfoTable = new UserInfoTable();
userInfoTable.setValue1(
allZipData.getUserInfoModuleList().get(i).getValue1()
);
userDatabase.userDao().insertUserInfo(userInfoTable);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
// notify UI here?
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: all zip data " + e.toString());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete: all data zipped");
}
});
I believe this line still needs some RxJava operations :
userDatabase.userDao().insertUserInfo(userInfoTable);
I believe the insertUserInfo in your dao should return a Completable.
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Clearly says that you're running on MainThread of your application, which leads to freezing of the screen. You should handle your queries or long running operations on background thread of your application.
Change this
observeOn(AndroidSchedulers.mainThread())
to
observeOn(Schedulers.io())
Use Map to perform operation. Check this
.subscribeOn(Schedulers.io())
.map {
}
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
}
.subscribeWith(new DisposableObserver<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
}
#Override
public void onError(Throwable throwable) {
}
})
I'm using rx libraries im my app to call some REST api on my server and to show the results on screen.
I'm also following the MVP design pattern. So I have a Presenter and an Interactor classes.
In MainInteractor.java I have the following method:
public Observable<Card> fetchCard(final String clientId, final CardFetchedListener listener) {
Log.i(TAG, "FetchCard method");
// Manipulate the observer
return CARDS
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "CARDS Completed");
}
})
.flatMap(new Func1<Card, Observable<Card>>() {
#Override
public Observable<Card> call(final Card card) {
return ResourceClient.getInstance(card)
.getIDCard()
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.w(TAG, "interactor -> fetchCard 2", throwable);
}
}
})
.flatMap(new Func1<CardMeta, Observable<Card>>() {
#Override
public Observable<Card> call(CardMeta cardMeta) {
card.setCardMeta(cardMeta);
saveOrUpdateCardToTheDb(card);
return Observable.just(card);
}
})
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "Completed body");
}
});
}
});
}
In the logs I can see the "Completed Body" string.
The above method is being called by MainPresenter.java class as follows:
interactor.fetchCard(clientId, this)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Card>() {
#Override
public void onCompleted() {
Log.i(TAG, "fetchCard onCompleted");
view.hideProgressDialog();
view.updateCardsAdapter(cards);
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "Fetch Card error ", e);
onFailure(parseThrowable(e));
}
#Override
public void onNext(Card card) {
if (card != null) {
Log.i(TAG, card.getTenant() + " was fetched and will be displayed");
}
}
});
The problem is that the onCompleted method in the Presenter class is never bein called. I have tried to call onCompleted myself and it worked, but the problem is I don't know actually when the observable has finished emitting cards.
What am I doing wrong here?
UPDATE
CARDS is also an observable that contains meta info. It is initialized using
Observable.from(tenants)
.filter(...).flatMap(// I'm using create operator here and it is calling its onCompleted method successflly);
My goal is to limit the number of parallel executing request by throwing an exception.
For example, I want only one executing request:
someApi.getUser(
result -> print("ok: " + result), exception -> print("error: " + exception)
); // this request will be executed in 5 seconds
someApi.getServerInfo(
result -> print("ok: " + result), exception -> print("error: " + exception)
); // there I want to catch exception like ExecutorIsBusy
How can I implement it with Retrofit 2.0?
I am not sure that throwing an Exception is the best way to do it, but I don't know your use case so I will not discuss this point :)
Anyway, the comment of #Daniel actually points to a pretty good direction. If you're using retrofit with OkHttp, it's the OkHttpClient that will handle the "concurrent requests" stuff. Reading the docs, you could see that OkHttp uses a Dispatcher to handle parallel asynchronous requests (Dispatcher docs).
So two interesting points are :
Method setMaxRequests(int maxRequests) : defines the maximum concurrent requests
Method executed(RealCall call) : actually executes a requests
I think you could do this to achieve your goal :
Create a custom Dispatcher class
Overrides the executed(RealCall call) method throwing an exception if the number of currents request is superior to maxRequests
Use the custom Dispatcher in the OkHttpClient you're using with retrofit
Using RxJava & taking the approach in the above comments, here is an example:
Assume that these operations are attached to buttons.
public void runObservable1(View view) {
if (!taskRunning){
try{
taskRunning = true;
subsonicService.runTask1()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<Result>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new Subscriber<Result>() {
#Override
public void onCompleted() {
taskRunning = false;
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Result result) {
//your logic here
}
});
} catch (IOException e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "Task is running you must wait", Toast.LENGTH_SHORT).show();
}
}
public void runObservable2(View view) {
if (!taskRunning){
try{
taskRunning = true;
subsonicService.runTask2()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<Result>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new Subscriber<Result>() {
#Override
public void onCompleted() {
taskRunning = false;
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Result result) {
//Logic here
}
});
} catch (IOException e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "Task is running you must wait", Toast.LENGTH_SHORT).show();
}
}
I am also not an expert at RxJava so there may be an operator that makes this easier.
I am having hard time understanding RX. In the following case, is it necessary to unsubscribe? Is there a way to automatically unsubscribe after the "call" function was executed?
Observable.create(new Observable.OnSubscribe<NumberInfo>() {
#Override
public void call(Subscriber<? super NumberInfo> subscriber) {
try {
// Store data to db
} catch (Exception e) {
Log.e(TAG, "Downloaded numberInfo was not added to cache.", e);
}
}
}).subscribeOn(Schedulers.newThread())
.subscribe();
I don't want to observe for any result that's why I omitted the classical .observeOn(AndroidSchedulers.mainThread())
Thx for explanation.
According to Rx contract, when the Observable fires onCompleted, the Observer unsubscribes. In your case, the contract is not respected because there is no subscriber.onCompleted() in your code.
If you just need something like "Fire and forget", you could try just:
Schedulers.io().createWorker().schedule(new Action0() {
#Override
public void call() {
try {
// Store data to db
} catch (Exception e) {
Log.e(TAG, "Downloaded numberInfo was not added to cache.", e);
}
}
});
It will execute on I/O Scheduler and your UI thread is safe.
IMO you should always have a return value. Your Store data to db routing surely has some return value, like a long specifying the row number or a boolean that indicates success. Having this approach, you can create a proper method:
public Observable<Long> storeToDb(final SomethingToStore storeMe) {
return Observable
.create(new Observable.OnSubscribe<Long>() {
#Override
public void call(Subscriber<? super Long> subscriber) {
long row = syncStore(storeMe);
if (row == -1) {
subscriber.onError(new Throwable("Cannot store " + storeMe.toString + " to DB."));
}
subscriber.onNext(row);
subscriber.onCompleted();
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
And you could use it like this:
storeToDb(storeThis)
.subscribe(new Observer<Long>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.e("STORING", "Something went south: " + e.getMessage());
}
#Override
public void onNext(Long row) {
Log.d("STORING", "Everything has been stored as record number: " + row);
}
});
When Observable is complete, RxJava unsubscribes automatically. You need to call subscriber.onComplete() to perform automatic unsubscription.