How can I achieve that doOnNext wait to the results of multiple asynchronous tasks?
For example -
public void getImages(User user) {
Flowable.create(new FlowableOnSubscribe<User>() {
#Override
public void subscribe(#io.reactivex.rxjava3.annotations.NonNull FlowableEmitter<User> emitter) throws Throwable {
emitter.onNext(user);
}
}, BackpressureStrategy.BUFFER)
.observeOn(Schedulers.io())
.doOnNext(user -> {
ArrayList<String> imagesUrls = user.getUrls();
for (String url : imagesUrls) {
storage.getReference().child("images").child(url).getBytes(ParametersConventions.FIREBASE_DOWNLOAD_IMAGE_MAX_SIZE).
addOnSuccessListener(bytes -> {
doSomething(bytes);
});
}
})
.doOnNext(user -> {
doSomething();
})
.doOnComplete(...);
}
and I want that the doOnNext which calls to doSomething will be called after all the asynchronous calls to download the images are finished.
Turn that API call into a reactive type and merge it into the main flow:
int max = ParametersConventions.FIREBASE_DOWNLOAD_IMAGE_MAX_SIZE;
public Completable downloadAsync(URL url) {
return Completable.create(inner -> {
storage.getReference()
.child("images")
.child(url)
.getBytes(max)
.addOnSuccessListener(bytes -> {
doSomething(bytes);
inner.onComplete();
});
});
}
Together:
Flowable.create(emitter-> {
emitter.onNext(user);
}, BackpressureStrategy.BUFFER)
.observeOn(Schedulers.io())
.concatMapSingle(user ->
Flowable.fromIterable(user.getUrls())
.concatMapCompletable(url -> downloadAsync(url))
.andThen(Single.just(user))
)
.doOnNext(user -> {
doSomething();
})
.doOnComplete(...);
doOnNext operator is fired every time there is a new item on a stream so it is not the best option for you. Try using map/flatMap/concatMap operator depending on your needs. If you need to make several calls and then do something with the data you can look at similar question I've already answered link: Chaining API Requests with Retrofit + Rx
in which you can find a way to make sequential network calls and then do whatever you want with a list of data :D
Related
I have asynchronous functions with firebase and other APIs that depend on each other. So, to start task B has to finish task A.
The async functions are retuning MyResult which can be a success or failure. Now I’m doing it in that way
when(val resullt1 = function1UseCase.getresult1()){
is MyResult.Success ->{
when(val result2 = function2UseCase.getResult2()){
is MyResult.Succes ->{
//Do something or call another async function
}
is MyResult.Failure ->{
//Do something or call another async function
}
}
}
is MyResult.Failure ->{
//Do something or call another async function
}
}
Is there a better way to do it? Because when I have more nested tasks the code doesn’t look very well.
Thanks!
You can create a simple extension. Something like that
inline fun <F, R> MyResult<F>.then(function: (F) -> MyResult<R>) = when (this) {
is MyResult.Succes -> {
try {
function(result)
} catch (throwable: Throwable) {
MyResult.Failure(your_error_handling_here)
}
}
is MyResult.Failure -> this
}
Annnd then it will be like that
when(val result = function1UseCase.getresult1().then { function2UseCase.getResult2() }) {
is MyResult.Success -> {
}
is MyResult.Failure -> {
//Do something or call another async function
}
}
You can slightly modify this if you need to have both results at the very end ;)
I have something like:
private Single<List<Data>> getFirstApiResponse() {
return Single.just(....)
/////
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private Single<AnotherData> getSecondApiResponse() {
return Single.just(....)
/////
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public void execute() {
//Here I need to run both observables one by one, and show result of each in View
// Code exetuting both
.subscribe(......);
}
How can I run two observables and subscribe on them in last method. In other words, I need to run method execute which will display in UI result of each Observables.
By the way, Observable not connected, they fetch different data (so I can run them asynchronous)
One way to do that is with flatMap:
public void execute() {
getFirstApiResponse()
.flatMap(response1 -> {
// getFirstApiResponse has completed
// ...
return getSecondApiResponse();
})
.subscribe(response2 -> {
// getSecondApiResponse has completed
// ...
}, error -> {
// One of the other operation has failed
});
}
You could look into the zip operator as well, depending on your needs. The downside to this solution is you are forced to combine your responses into a pair or another suitable datastructure, which may not make sense for you.
public void execute() {
Single.zip(getFirstApiResponse(), getSecondApiResponse(),
(first, second) -> {
//now you have both
return Pair.of(first, second);
}).subscribe(pair -> {/**do stuff**/});
}
I have two async methods, that got to be called while one operation. Each method could be completed successfully or retrieve with error. On case of error, I got to retry call each method once again, with delayed of 2 sec. Mean, I should call both methods, despite of result of one of them. In error callback I want to know in which method error occured, or in both methods.
It seems I should use Completable for this, but I'm absolutely newbie in Rx.
private void method1(final CompletableEmitter e, String path){
Database.getInstance().getReference(path).addListener(new Listener() {
#Override
public void onDataChange(Data data) {
//todo something
e.onComplete();
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(new Throwable(databaseError.getMessage()));
}
});
}
Method2 is the same.
The following code doesn't work properly.
Completable completable1 = Completable.create(method1(e););
Completable completable2 = Completable.create(method2(e););
completable1
.doOnError(…)
.retry(1)
.andThen(completable2 //never called if completable1 gets onError each time
.retry(1)
.doOnError(…))
.subscribe(…).dispose();
You have a lot of ways to do this. I'm going just to limit to explain how to achieve this using two Completables
Let's say you have two completables:
Completable doSomething = ...
Completable doSomethingElse = ...
To execute these sequentially,
you can concatenate them using andThen operator. Then to delay a retry when an error occurs, you can use retryWhen:
doSomething.andThen(doSomethingElse)
.retryWhen { Flowable.timer(2, TimeUnit.SECONDS) }
.subscribe()
This snippet above will retry infinitely if an error is permanently occurring. To go beyond, you can limit the number of tries using:
.retryWhen { errors ->
val retryCounter = AtomicInteger()
errors.flatMap {
if (retryCounter.getAndIncrement() <= 3)
Flowable.timer(2, TimeUnit.SECONDS)
else Flowable.error(it)
}
}
If you want to retry only when a given type of error occurs, you can use:
.retryWhen { errors ->
val retryCounter = AtomicInteger()
errors.flatMap {
if (it is YourSpecficError && retryCounter.getAndIncrement() <= 3)
Flowable.timer(2, TimeUnit.SECONDS)
else Flowable.error(it)
}
}
In the case you want to retry each one independently, you can use:
doSomething.retryWhen { ... }
.andThen(doSomethingElse.retryWhen { ... })
.subscribe()
In addition, in order to avoid the retryWhen logic duplication, you could encapsulate this in an extension function:
fun Completable.retryDelayed(): Completable {
return this.retryWhen { errors ->
val retryCounter = AtomicInteger()
errors.flatMap {
if (it is YourSpecficError && retryCounter.getAndIncrement() <= 3)
Flowable.timer(2, TimeUnit.SECONDS)
else Flowable.error(it)
}
}
}
If you want to run your completables in parallel you ca use merge operator:
Completable doAll = Completable.merge(listOf(doSomething, doSomething))
I need to get the categories, and then get the channels of that categories, and finally invoke a method when all categories and their channels are retrieved from the server. I guess that I need to use RxJava, but I could not find a similar implementation. (Preferably without using lambda/retrolambda expressions).
#GET("/api/{categoryId})
Call<Category> getCategory(#Path("categoryId") String categoryId)
private void getCategories() {
for (Tab t : tabs) {
Call<Category> getCategory = videoAPI.getCategory(t.getId());
getCategory.enqueue(new Callback<Category>() {
#Override
public void onResponse(Call<Category> call, Response<Category> response) {
Category cat = response.body();
categories.add(cat);
// I will call the getChannels(String categoryId) method here,
// however I think implementing RxJava would be much better.
}
#Override
public void onFailure(Call<Category> call, Throwable t) {
Log.i(TAG, "failure: " + t.getLocalizedMessage());
}
});
}
}
You can do that with
Observable
.fromArray(/*your list of observables go here, make sure that within flatMap you get as type Observable<T>, not Observable<List<T>>*/)
.flatMap(/*here you subscribe every item to a different thread, so they're parallel requests: subscribeOn(Schedulers.computation())*/)
.subscribe (/*each request*/,/*error*/,/*completed all requests*/)
Now your request needs to be of type Observable
#GET("/api/{categoryId})
Observable<Category> getCategory(#Path("categoryId") String categoryId)
Example code in Java:
// Setup a list of observables
List<Observable<Category>> parallelRequests = new ArrayList<>();
for (Tab t : tabs) {
parallelRequests.add(videoAPI.getCategory(t.getId()));
}
Observable[] array = new Observable[parallelRequests.size()];
// Convert the list to array
parallelRequests.toArray(array);
Observable
.fromArray(array)
.flatMap(observable -> observable.subscribeOn(Schedulers.computation()))
.subscribe(o -> {
// each request is fulfilled
}, Throwable::printStackTrace, () -> finishFunctionHere());
Or if you're using Kotlin
Observable
// The asterisk is called "spread operator": It converts an array to vararg
.fromArray(*tabs.map { api.getCategory(it.getId()) }.toTypedArray())
.flatMap { it.subscribeOn(Schedulers.computation()) }
.subscribe({ category ->
// onEach
}, Throwable::printStackTrace, {
// All requests were fulfilled
})
I'm using Retrofit to get list of pallet types from Api.
This is how my call looks like.
#GET("pallettypes")
Observable<List<PalletType>> getPalletTypes();
Then I have some function that gets the response from the api and map it (I don't know if I'm using the map function as it should - new to RxJava)
private static Observable<List<PalletType>> getTypes() {
return getApiService().getPalletTypes()
.map(response -> {
//Here i need some code to get the response from the api and put it in Store.palletTypes()
}
return response;
And then to call the function in the onViewCreated part.
getTypes().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(() ->{
})
.doOnTerminate(() -> {
})
.subscribe(response -> {
PalletsManager.getInstance().setTypes(response);
populateTypes(response);
}, throwable -> {
});
I need populateTypes function to show the type using a custom view
public void populateTypes (List<PalletType> palletTypes) {
for(PalletType type : palletTypes) {
palletView = new PalletView(getContext());
palletView.setLabel(type.getType());
delivered_pallets.addView(palletView);
}
}
This is my idea but it doesn't work because I never get in the .subscribe block and noting is shown.