RxJava Loop for doing async work - android

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());
}
});
}

Related

Rxjava2: Usage of Single & CompositeDisposable

Background:
I'm trying to load an image with Glide and rxjava2 (to make use of background thread).
I'm still confused how each component of rxjava2 api works.
So below are my questions.
Q1: Is it impossible to add Single to CompositeDisposable?
Whenever I try to add Single into CompositeDisposable, Android Studio warns me that there is type mismatch.
However if I call toObservable() before subscribeOn, error message goes away.
Q2: Correct my usage of rxjava2 with Glide if there are some problems.
private void loadImages(final String imageUri) {
Single<GlideRequest<Drawable>> glideSingle = Single.fromCallable(
new Callable<GlideRequest<Drawable>>() {
#Override
public GlideRequest<Drawable> call() {
return GlideApp.with(MainActivity.this).load(imageUri);
}
}
);
// mSubscription is just new CompositeDisposable()
mSubscription.add(
glideSingle
.toObservable() // I have to call this to suppress error message
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(
new DisposableObserver<GlideRequest<Drawable>>() {
#Override
public void onNext(GlideRequest<Drawable> drawableGlideRequest) {
RequestOptions backgroundTransformOptions = new RequestOptions()
.transforms(
new CenterCrop(),
new BlurTransformation(60),
new ColorFilterTransformation(
ContextCompat.getColor(MainActivity.this, R.color.colorBackgroundOverlay))
);
drawableGlideRequest
.into(mAlbumArt);
drawableGlideRequest
.apply(backgroundTransformOptions)
.into(mMainBackground);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
}
)
);
}
Rather then subscribeWith you should be able to use the regular subscribe method.
This will return a Disposable, e.g. without lambda support this would look something like this:
mSubscription.add(
glideSingle
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<GlideRequest<Drawable>>() {
#Override
public void accept(GlideRequest<Drawable> drawableGlideRequest) throws Exception {
RequestOptions backgroundTransformOptions = new RequestOptions()
.transforms(
new CenterCrop(),
new BlurTransformation(60),
new ColorFilterTransformation(
ContextCompat.getColor(MainActivity.this, R.color.colorBackgroundOverlay))
);
drawableGlideRequest
.into(mAlbumArt);
drawableGlideRequest
.apply(backgroundTransformOptions)
.into(mMainBackground);
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
}
})
);

How to wait for retrofit response in rxjava

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()

Retrofit Rx Java Requests

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() {
}
});

How can I create a stream in RxJava2 (Android) that has no input value, but produces a String?

In Android, I want to use the call AdvertisingIdClient.getAdvertisingIdInfo(getContext()).getId() on a separate thread (IO-thread) and handle the string on the main thread.
I wan't to do this with RxJava2.
This is what I have now: (which works)
SingleOnSubscribe<String> source = new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
e.onSuccess(AdvertisingIdClient.getAdvertisingIdInfo(getContext()).getId());
}
};
Single.create(source)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Timber.e(throwable.getMessage());
}
})
.subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
advertisingId = s;
}
});
What I would prefer, which is purely taste, is if I could "just" create the stream and handle it all in the flow of methods. Such as: (warning, super pseudo code)
Completable
.empty()
.produce(() -> String {
return makeString();
})
.sub/obs-On()...
.subscribe(coolString -> {mVariable = coolString})
So, Make an Observable and turn it into an Observable by executing some function.
Just use defer or fromCallable like in this example:
Observable<String> stringObservable = Observable.fromCallable(() -> {
return getStuff();
});
Test
#Test
public void fromCallable() throws Exception {
Observable<String> stringObservable = Observable.fromCallable(() -> {
return getStuff();
});
ExecutorService executorService = Executors.newSingleThreadExecutor(runnable -> {
return new Thread(runnable, "myFancyThread");
});
Scheduler scheduler = Schedulers.from(executorService);
TestObserver<String> test = stringObservable.subscribeOn(scheduler)
.test();
test.await()
.assertResult("wurst");
assertThat(test.lastThread().getName()).contains("myFancyThread");
}
private String getStuff() {
return "wurst";
}

Connecting RxJava Observables in layers

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.

Categories

Resources