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
Related
When I get data from Cloud Firestore in android. It returns an empty variable. It should return the value of the flag true or false.
public boolean checkIfUserAlreadyExists(final String email) {
final boolean[] flag = {false};
db.collection(context.getString(R.string.db_collection_users))
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
if (document.getData().get(context.getString(R.string.db_field_email)).toString() != null && document.getData().get(context.getString(R.string.db_field_email)).toString().equals(email)) {
Toast.makeText(context, "User Already exits", Toast.LENGTH_SHORT);
Log.e(TAG, "User already exists" + document.getData().get("email").toString());
flag[0] = true;
} else {
Log.e(TAG, "User do not exists");
}
}
} else {
Log.e(TAG, "Error getting documents.", task.getException());
}
}
});
return flag[0];
}
flag[0] should return true but it returns false when called in another activity.
addOnCompleteListener works asynchronously.
That is you are returning flag[0] immeadiately after the OnCompleteListener is set. By that time the onComplete() would not have called and hence the flag[0] still have the initial value. This compiler expected this type of error that's why it's warned you that you can not change non final variables inside the callback method. But you bypassed it in a strange way by making it an array 😀
You have can solve this in multiple ways
Solution 1
Instead of returning the value, access the value from the callback
public void checkIfUserAlreadyExists(final String email) {
db.collection(context.getString(R.string.db_collection_users))
.get()
.addOnCompleteListener(new OnCompleteListener < QuerySnapshot > () {
#Override
public void onComplete(#NonNull Task < QuerySnapshot > task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document: task.getResult()) {
if (document.getData().get(context.getString(R.string.db_field_email)).toString() != null && document.getData().get(context.getString(R.string.db_field_email)).toString().equals(email)) {
Toast.makeText(context, "User Already exits", Toast.LENGTH_SHORT);
Log.e(TAG, "User already exists" + document.getData().get("email").toString());
// User exists.
userTheResult(true);
return;
} else {
Log.e(TAG, "User do not exists");
}
}
// No user exists.
userTheResult(false);
} else {
Log.e(TAG, "Error getting documents.", task.getException());
}
}
});
}
Solution 2
You can use this class to make a thread waiting until the result is obtained. This way you don't have to change your code much. What you have to do is simply add the class ThreadLockedTask<T> as it is to your project and use it as in the example given.
import java.util.concurrent.atomic.AtomicReference;
/**
* #author Ahamad Anees P.A
* #version 1.0
* #param <T> type
*/
public class ThreadLockedTask<T> {
private AtomicReference<ResultWrapper<T>> mReference;
public ThreadLockedTask() {
mReference = new AtomicReference<>(new ResultWrapper<T>());
}
public T execute(Runnable runnable) {
runnable.run();
if (!mReference.get().mIsSet)
lockUntilSet();
return mReference.get().mResult;
}
private void lockUntilSet() {
synchronized (this) {
while (!mReference.get().isSet()) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public void setResult(T result) {
synchronized (this) {
ResultWrapper<T> wrapper = mReference.get();
wrapper.setResult(result);
wrapper.setIsSet(true);
notify();
}
}
public static class ResultWrapper<T> {
private boolean mIsSet;
private T mResult;
public boolean isSet() {
return mIsSet;
}
public T getResult() {
return mResult;
}
void setIsSet(boolean isCompleted) {
this.mIsSet = isCompleted;
}
void setResult(T result) {
this.mResult = result;
}
}
}
Usage
public boolean checkIfUserAlreadyExists(final String email) {
ThreadLockedTask < Boolean > task = new ThreadLockedTask < > ();
boolean flag = task.execute(new Runnable() {
#Override
public void run() {
db.collection(context.getString(R.string.db_collection_users))
.get()
.addOnCompleteListener(new OnCompleteListener < QuerySnapshot > () {
#Override
public void onComplete(#NonNull Task < QuerySnapshot > task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document: task.getResult()) {
if (document.getData().get(context.getString(R.string.db_field_email)).toString() != null && document.getData().get(context.getString(R.string.db_field_email)).toString().equals(email)) {
Toast.makeText(context, "User Already exits", Toast.LENGTH_SHORT);
Log.e(TAG, "User already exists" + document.getData().get("email").toString());
task.setResult(true);
return;
} else {
Log.e(TAG, "User do not exists");
}
}
} else {
Log.e(TAG, "Error getting documents.", task.getException());
}
task.setResult(false);
}
});
}
});
return flag;
}
Before I always used retrofit to load data. It's very easy to handle or get status code of the response.
But now I want to use Retrofit with RxJava but I don't know how to handle or get the https status code of the response in onNext method.
progressDialog.show();
Observable<ResponseData> observable = apiService.getData();
compositeDisposable.add(observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<ResponseData>() {
#Override
public void onNext(ResponseData responseData) {
Log.e("pppp", "onNext: " + responseData.toString());
}
#Override
public void onError(Throwable e) {
progressDialog.dismiss();
Log.e("pppp", "onError: " + e.getMessage());
}
#Override
public void onComplete() {
progressDialog.dismiss();
Log.e("pppp", "onComplete");
}
})
);
Everyone, please help me to solve this problem.
Thanks!
You should wrap your ResponseData inside Response as
Observable<Response<ResponseData>> observable = apiService.getData();
Then inside onNext
#Override
public void onNext(Resposne<ResponseData> response) {
int statusCode = response.code();
}
and for error
#Override
public void onError(Throwable e) {
((HttpException) e).code();
}
responseData.code gives you the status code
int statusCode = responseData.code();
for getting status
Observable<ResponseData> observable = apiService.getData();
compositeDisposable.add(observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<ResponseData>() {
#Override
public void onNext(ResponseData responseData) {
int statusCode = responseData.code();
// here you get your status code
Log.e("statusCode ", "onNext: " + statusCode );
}
#Override
public void onError(Throwable e) {
progressDialog.dismiss();
Log.e("pppp", "onError: " + e.getMessage());
}
#Override
public void onComplete() {
progressDialog.dismiss();
Log.e("pppp", "onComplete");
}
})
);
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);
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();
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.