I had 2 tables TimeStamps and Infraction, I want to do
something like that using retrofit with Rx Android :
Request-> I get TimeStamps (if it's changed)
-> I send new request to get Infractions
else I display infractions from database
this is what I did using Retrofit, is that correct ??
Observable<TimeStamps> callTimeStamp = apiInterface.getTimeStamp();
TimeStamps stamps = realm.where(TimeStamps.class).findFirst();
callTimeStamp.flatMap(new Function<TimeStamps, ObservableSource<List<Infraction>>>() {
#Override
public ObservableSource<List<Infraction>> apply(TimeStamps timeStamps) throws Exception {
if(!timeStamps.getInfractionTimeStamps().equalsIgnoreCase( stamps.getInfractionTimeStamps()))
return apiInterface.getInfractions();
else
return null;
}
}).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<List<Infraction>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<Infraction> infractions) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
No
TimeStamps stamps = realm.where(TimeStamps.class).findFirst();
This obtains stamps on the current thread
if(!timeStamps.getInfractionTimeStamps().equalsIgnoreCase( stamps.getInfractionTimeStamps()))
This attempts to access that stamps instance on a different thread, so you'll get an IllegalStateException
return null;
Even if it did work, this line would make RxJava2 throw a NullPointerException
.subscribeOn(Schedulers.newThread())
This could easily be Schedulers.io() instead so that it wouldn't create too many threads (although then of course you should make sure you use try(Realm realm = ...) or finally { realm.close() })
.subscribe(new Observer>() {
This is wrong unless you "properly implement onSubscribe" which is not expected at all, this should be new DisposableObserver<List<Infraction>>().
In which case your Retrofit interface should probably expose Single<T>, as singles automatically unsubscribe when done.
Single<TimeStamps> callTimeStamp = apiInterface.getTimeStamp();
callTimeStamp.flatMap((timeStamps) -> {
try(Realm realm = Realm.getDefaultInstance()) {
TimeStamps stamps = realm.where(TimeStamps.class).findFirst();
if(!timeStamps.getInfractionTimeStamps().equalsIgnoreCase( stamps.getInfractionTimeStamps()))
return apiInterface.getInfractions();
else
return Single.never();
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<List<Infraction>>() {
#Override
public void onNext(List<Infraction> infractions) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
Related
I'm new in RxJava. I have currently executed three API calls parallel which is independent of each other via Retrofit using Single.Zip Operator. On getting a successful response of all three API calls, I have to insert the data from all three APIs into Room database into Different entities which takes 20 seconds.
So I need to execute database operations inside Single.Zip operator. Because the logic is written inside onSuccess method running away before Database Operation performed.
I have tried to take separate Observer for performing database operation but didn't work.
public void callOfflineDataAPIs() {
setIsLoading(true);
Single<BaseResponse<ProductResponse>> single1 = getDataManager().getOfflineProductListApiCall(getDataManager().getLastTimeStampOfflineProductCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<LocationResponse>> single2 = getDataManager().getOfflineLocationListApiCall(getDataManager().getLastTimeStampOfflineLocationCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<OfflineMasterData>> single3 = getDataManager().getOfflineMasterDataListApiCall(getDataManager().getLastTimeStampOfflineMasterCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribeWith(new DisposableSingleObserver<List<Boolean>>() {
#Override
public void onSuccess(List<Boolean> apiCalls) {
setIsLoading(false);
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess");
boolean isSync = true;
for (int i = 0; i < apiCalls.size(); i++) {
if (!apiCalls.get(i)) {
isSync = false;
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess- apiCalls.get(i)", i);
callOfflineDataAPIs();
break;
}
}
if (isSync) {
LogHelper.e(TAG, "IF-isSync");
if (BuildConfig.IS_CLIENT_BUILD) {
LogHelper.e(TAG, "IF-isSync-IS_CLIENT_BUILD-true");
getDataManager().setCurrentWarehouseKey(1);
getNavigator().onGoButtonClick();
} else {
LogHelper.e(TAG, "ELSE-isSync-IS_CLIENT_BUILD-false");
getWarehouseList();
}
}
}
#Override
public void onError(Throwable e) {
LogHelper.e(TAG, "DisposableSingleObserver- Throwable");
setIsLoading(false);
String errorMessage = new NetworkError(e).getAppErrorMessage();
getNavigator().exitApplicationOnError(errorMessage);
}
});
}
Logic written inside onSuccess Method execute once all DB Operation performed.
You can modify your code to something like:
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io())
.map(new Function<List<Boolean> apiCalls, List<Boolean> apiCalls>() {
#Override
public List<Boolean> apiCalls apply(List<Boolean> apiCalls) throws Exception {
// perform database operations here
return apiCalls;
}
})
.observeOn(getSchedulerProvider().ui())
.subscribe(new Observer<List<Boolean>>() {
#Override
public void onNext(User user) {
// Do something
}
#Override
public void onError(Throwable e) {
// Do something
}
#Override
public void onComplete() {
// Do something
}
});
I use retrofit2 with rxjava extension.
I have a list of REST API urls and want to do this:
for each
check whether a corresponding file locally exists
if yes: call the API and store the response or the HTTP error
if not: store a customized error
return the list of those results
My problem is: apply returns (with an empty RequestResult) before the server response is received. I think, I understand why, but I don't know how to fix it, because I need to return a RequestResult and not the Retrofit observable.
How can this be solved?
Here is my code:
#GET
Observable<Response<ResponseBody>> enroll(#Url String url);
class RequestResult {
CustomException error;
Response<ResponseBody> response;
}
Observable<ClassOfListItem> observable = Observable.fromIterable(listOfItems);
observable
.flatMap(new Function<ClassOfListItem, ObservableSource<RequestResult>>() {
#Override
public ObservableSource<RequestResult> apply(ClassOfListItem listItem) throws Exception {
RequestResult requestResult = new RequestResult();
if (fileExists(listItem.url)) {
Observable<Response<ResponseBody>> callObservable = restAPI.enroll(listItem.url)
.subscribeOn(Schedulers.io());
callObservable
.subscribe(new DisposableObserver<Response<ResponseBody>>() {
#Override
public void onNext(Response<ResponseBody> responseBodyResponse) {
onPremiseEnrollmentResult.response = responseBodyResponse;
}
#Override
public void onError(Throwable e) {
onPremiseEnrollmentResult.error = new CustomException(e);
}
#Override
public void onComplete() {
}
});
}
else {
requestResult.error = new CustomException("file not found");
}
return Observable.just(requestResult);
}
}
.toList()
.observerOn(AndroidScheduler.mainThread())
.subscribe(new DisposableSingleObserver<List<RequestResult>>() {
#Override
public void onError(Throwable e) {
Log.d("onError", e.getMessage());
}
#Override
public void onSuccess(List<RequestResult> requestResults) {
// parse results
}
}
)
The flatMap() operator allows you to turn one observable into a different observable. You have a nested observer chain inside your apply() which is not part of the observer chain, so it will be empty because it has not completed yet.
To fix this, when the file exists, return the observable.
observable
.flatMap(new Function<ClassOfListItem, ObservableSource<RequestResult>>() {
#Override
public ObservableSource<RequestResult> apply(ClassOfListItem listItem) throws Exception {
RequestResult requestResult = new RequestResult();
if (fileExists(listItem.url)) {
return restAPI.enroll(listItem.url)
.subscribeOn(Schedulers.io());
}
return Observable.error( new CustomException("file not found") );
}
}
.toList()
.observerOn(AndroidScheduler.mainThread())
.subscribe(new DisposableSingleObserver<List<RequestResult>>() {
#Override
public void onError(Throwable e) {
Log.d("onError", e.getMessage());
}
#Override
public void onSuccess(List<RequestResult> requestResults) {
// parse results
}
}
If you need to capture both errors and successes into the list, then you can add map() operator to wrap RequestResult around the response and onErrorResumeNext() to wrap RequestResult around the error before the toList() operator.
If you are making api call on background thread then what you can do is invoke it synchronously....in your case your retrofit api method would change to following
Call<Response<ResponseBody>> enroll(#Url String url);
and you'd invoke by calling restAPI.enroll(listItem.url).execute()
I am using Compressor library, i want compress the images using RxJava. Following is the example from the library documentation.
new Compressor(this)
.compressToFileAsFlowable(actualImage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<File>() {
#Override
public void accept(File file) {
compressedImage = file;
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
throwable.printStackTrace();
showError(throwable.getMessage());
}
});
This works very well. Now i want to compress a list of images, how can i use this technique to get a list of compressed file paths?
I tried adding this method in a for loop but the returned list was empty because the accept method was not even called once and the code reached the return statement. Following is my method
#NonNull
private ArrayList<String> compressFiles(ArrayList<String> files, File directory) {
final ArrayList<String> filesToReturn = new ArrayList<>();
for (final String filepath : files) {
new Compressor(this)
.compressToFileAsFlowable(new File(filepath))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<File>() {
#Override
public void accept(File file) {
filesToReturn.add(file.getAbsolutePath());
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
throwable.printStackTrace();
}
});
}
return filesToReturn;
}
How can i change this method using RxJava so accept method only triggers when all the files have been compressed and list is filled?
I tried searching for RxJava loops/Flatmap but i couldn't figure them out. I am new to RxJava. Any help or pointers would be highly appreciated.
In RxJava, you would use Observable.fromIterable():
Observable.fromIterable(files)
.flatMapIterable(files -> files)
.flatMap(filepath -> new Compressor(this).compressToFileAsFlowable(new File(filepath)))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<File>() {
#Override
public void accept(File file) {
compressedImage = file;
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
throwable.printStackTrace();
showError(throwable.getMessage());
}
});
Here are two more possible results:
Completable will be called when all images have been processed
Flowable will push results to subscriber as they are finished processing (honors backpressure)
Please look at subscribeOn. I use the computation scheduler, because cpu bound work should be executed on a bounded threadpool. IO is unbounded.
When you subscribe to Completeable or Flowable you have to apply observeOn with Android-MainUI-Scheduler.
Rember that subscribing to an Flowable/Observable should only be done, when state muste be changed. Otherwise compose observables together.
// Will call complete, when all files finish
private Completable compressFiles(ArrayList<String> files) {
return Flowable.fromIterable(files)
.subscribeOn(Schedulers.io())
.map(new Function<String, File>() {
#Override
public File apply(String s) throws Exception {
return Paths.get(s).toFile();
}
})
.flatMapCompletable(new Function<File, CompletableSource>() {
#Override
public CompletableSource apply(File file) throws Exception {
return new Compressor(appContext)
.compressToFileAsFlowable(file)
.singleOrError()
.toCompletable();
}
});
}
// Will push each file to subscriber as one finishes
private Flowable<File> compressFiles2(ArrayList<String> files) {
return Flowable.fromIterable(files)
.subscribeOn(Schedulers.io())
.map(new Function<String, File>() {
#Override
public File apply(String s) throws Exception {
return Paths.get(s).toFile();
}
})
.flatMap(new Function<File, Publisher<? extends File>>() {
#Override
public Publisher<? extends File> apply(File file) throws Exception {
return new Compressor(appContext)
.compressToFileAsFlowable(file)
.subscribeOn(Schedulers.computation());
}
});
}
I have 3 layers in my app. Layer1 subscribes to Observable from layer2. Layer2 subscribes to layer3 in order to emit returned data to layer1.
Layer1
layer2.getData(data).subscribe(newData -> {Log.d("onNext", "returned");},
throwable -> {Log.d("onError", throwable.getMessage());});
Suppose layer3 has a method called downloadDataFromApi(data);
public Observable<Data> getData(String data) {
return Observable.create(new Observable.OnSubscribe<Data>() {
#Override
public void call(Subscriber<? super Data> subscriber) {
Data data = new Data();
subscriber.onNext(data);
subscriber.onCompleted();
// Can't find a way to connect to layer3.
}
});
}
What do I need to do in layer2's getData() method? I basically want to have logics before returning Observable back to layer1.
Does that make sense?
Just return the Observable directly. Then layer1 handles subscription as usual.
class Layer2 {
public Observable<Data> getData(String data) {
return layer3.getData(data);
}
}
From what I see you have 3 layers (presentation, business logic, data access).
So what you could do is the following:
class PresentationLayer {
private BusinessLogicLayer layer;
PresentationLayer() {
layer = new BusinessLogicLayer();
}
public void showName() {
layer.getNameWithoutRxPrefix()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
#Override
public void accept(String name) throws Exception {
// show name somewhere
Log.d("PresentationLayer", "name: " + name);
}
});
}
}
class BusinessLogicLayer {
private DataAccessLayer layer;
BusinessLogicLayer() {
layer = new DataAccessLayer();
}
public Observable<String> getNameWithoutRxPrefix() {
return layer.getName()
.map(new Function<String, String>() {
#Override
public String apply(String name) throws Exception {
return name.replace("Rx", "");
}
});
}
}
class DataAccessLayer {
public Observable<String> getName() {
return Observable.just("RxAndroid");
}
}
As you can see, I return an Observable in my data access layer (getName), and chain another method to it in my business logic method (map) before returning it to the presentation layer.
I'm very new to RXJava.
I have a function called politelyrefresh() that concats two observables together, but the functions in these two observables only run the first time I called politeRefresh, I'm not sure this is the right way to do it. What I want is run this function inside the observables everytime.
public void politelyRefresh() {
Observable.concat(refreshStoreDataObservable, refreshProjectDataObservable)
.finallyDo(()-> {
try {
//someother other long runnning-network requests
} catch (Exception e) {
Log.e(TAG, "politelyRefresh finallyDo Error", e);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(reloadUiFromLocalStorageSubscriber);
}
//the other observable is pretty much the same but making another request
Observable<String> refreshStoreDataObservable = Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
//DOESN'T GET HERE SECOND TIME!
Store.syncStores(new ListCallback() {
#Override
public void syncSuccess() {
getSyncStateManager().setStoresRefreshed();
subscriber.onCompleted();
}
#Override
public void syncError() {
subscriber.onError(new Throwable("SYNC STORES ERROR"));
getSyncStateManager().setStoresSyncCompleted();
}
});
}
});
Subscriber<String> reloadUiFromLocalStorageSubscriber = new Subscriber<String>() {
#Override
public void onCompleted() {
if (mStoreRefreshLayout != null){
mStoreRefreshLayout.setRefreshing(false);
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "reloadUiFromLocalStorageSubscriber: onError", e);
if (mStoreRefreshLayout != null){
mStoreRefreshLayout.setRefreshing(false);
}
}
#Override
public void onNext(String s) {
Log.d(TAG, "reloadUiFromLocalStorageSubscriber: onNext " + s);
}
};
I think you're looking for Observable.defer(). What this basically does is defer the creation of the Observable to when it is being subscribed to.
Here's a quick example:
public class Refresher {
Refresher() {
politelyRefresh();
politelyRefresh();
}
public void politelyRefresh() {
Observable.defer(() -> Observable.concat(refreshProjectData(), refreshStoreData()))
.map(this::processData)
.subscribe(this::printData);
}
private Observable<String> refreshStoreData() {
System.out.println("StoreData Refreshed");
return Observable.just("data1","data2","data3");
}
private Observable<String> refreshProjectData() {
System.out.println("ProjectData Refreshed");
return Observable.just("Project1","Project2", "Project3");
}
private String processData(String data) {
return data + " processed";
}
private void printData(String data) {
System.out.println(data);
}
}
If you instantiate our refresher object, you'll get
StoreData Refreshed
StoreData Refreshed
Project1 processed
Project2 processed
Project3 processed
data1 processed
data2 processed
data3 processed
StoreData Refreshed
StoreData Refreshed
Project1 processed
Project2 processed
Project3 processed
data1 processed
data2 processed
data3 processed
If you'd like something to run on a different thread, you'd specify that on the specific observable you're looking to run on a non-ui thread.
So, for example, you might want to run the Observable in politelyRefresh on a background thread and subscribe to it on the UI thread. The creation of the other Observables will happen in a background thread too!
I finally got this to work by move the subscriber from an class instance to inside the .subscribe() function(). I have no idea why this is happening.
Observable.concat(refreshStoreDataObservable, refreshProjectDataObservable)
.finallyDo(()-> {
try {
//someother other long runnning-network requests
} catch (Exception e) {
Log.e(TAG, "politelyRefresh finallyDo Error", e);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( new Subscriber<String>() { /*rest of code */}); //**here