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**/});
}
Related
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
A, B, C are objects
All function calls are made to a Rooms DB
This code snippet is inside a ViewModel
repo = Repository
So I'm making an android app (can't provide details) and for a particular screen I need to do the following.
My first call is repo.getInfo, which returns a Single Observable ListOfA: Single<List<A>> //perform some operations
for every element of ListOfA I need to call another function repo.getB(A) which returns a Single Observable ListOfB: Single<List<B>> //perform some operations
for every element of ListOfB I need to call another function repo.getC(B) which returns a Single Observable ListOfC: Single<List<C>> //perform some operations
after I have the required data I need to call another function that combines the data to display on the UI.
Now I can't get this to work. Here's what I've tried. But the flow stops at the line marked THIS LINE and jumps to subscribe block.
Individual calls to the functions work so the data is not the problem.
I'm pretty new at this and quite frankly out of my depth. Any help or hint is appreciated. Thanks
localListOfA = emptyList<A>()
localListOfB = emptyList<B>()
localListOfC = emptyList<C>()
compositeDisposable.add(
getInfo.map{listOfA ->
localListOfA.addAll(listofA)
listOfA.map {elementA -> ////THIS LINE
getB(elementA.id).map{listOfB ->
listOfB.filter {
//some logic to select a few objects
}
}.map { it // filtered list of B
localListofB.addAll(it)
localListOfB.last() //I only need the top element of this list
}.map{elementB ->
getC(elementB.id).map{ listOfC ->
localListOfC.addAll(listOfC)
//do some operations
}
}
}
}
.subscribeOn(DEFAULT_CACHED_SCHEDULERS)
.observeOn(AndroidSchedulers.mainThread())
.doOnError(/*take log*/)
.subscribe{
prepareUi()
}
)
You can flatten a List into an Observable using .flattenAsObservable
getInfo // Single<List<A>>
.doOnSuccess { localListOfA.addAll(it) } // Side effect, adding to localListOfA
.flattenAsObservable { it } // Observable<A>
.flatMapSingle { elementA -> getB(elementA.id) } // Observable<List<B>>
.map { it.filter { true } } // Some logic to select a few objects from B
.doOnNext { localListOfB.addAll(it) } // Side effect, adding to localListOfB
.map { it.last() } // Observable<B>, only the last element
.flatMapSingle { elementB -> getC(elementB.id) } // Observable<List<C>>
.doOnNext { localListOfC.addAll(it) } // Side effect, adding to localListOfC
.flatMapIterable { it } // Observable<C>
Now, you mentioned you need to combine this data somehow. In Rx you can nest chains in order to access the intermediate data. For example, if you have a call that returns a Single<Foo> and you need Foo for the function getBar(foo: Foo): Single<Bar>, one way of achieving this is as follows:
getFoo().flatMap { foo -> // .concatMap, .switchMap
getBar(foo).map { bar ->
// Use both foo and bar
}
}
I have a specific task to get several packs of data from server by calling same request several times. While answers contains more flag - i have to recall this request.
It seems something like this:
fun getData(some params): Single<Response<DataEntity>>
//
repository.getData(params)
.flatMap {
if (it.body()?.more == false)
more = false
else (
// here i want to repeat my request
// repository.getData(params) to get more data
)
}
.flatMap {// here i want to get all the data from
//previous requests to save to db etc.
}
Maybe i have to use something like repeatWhen or repeautUntil operators but i can't find the solution for now. Please help!)
You can use the concatMap operator in a recursive way, and as exit condition return just the result:
Single<Response<DataEntity>> getDataAndContinue(params) {
return getData(params)
.concatMap(new Func1<Response<DataEntity>, Single<Response<DataEntity>>>() {
#Override
public Single<Response<DataEntity>> call(Response<DataEntity> response) {
if (!response.body().hasMore()) {
return Single.just(response);
}
return Single.just(response)
.concatWith(getDataAndContinue(params));
}
});
}
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))
So, I have a Repository class, which has a search() method which creates a zipped Single and returns data to a listener.
Inside of this method, I need to call 4 services and zip their results. The services return different data types, from which I build the SearchResult object.
The method looks like this:
fun search() {
Single.just(SearchResult.Builder())
.zipWith(
service1.search()
.subscribeOn(Schedulers.io())
.onErrorReturnItem(emptyList()),
{ result, data -> result.apply { this.data1 = data } })
.zipWith(
service2.search()
.subscribeOn(Schedulers.io())
.onErrorReturnItem(emptyList()),
{ result, data -> result.apply { this.data2 = data } })
// Other two services done in the same way
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ listener.onSearchComplete(it.build()) },
{ listener.onSearchFailed() })
}
The implementation of search() in the services looks like this:
fun search(): Single<List<DataTypeX>> =
Single.create<List<DataTypeX>> { subscriber ->
makeNetworkRequest()
?.let{
//logic omitted for clarity
subscriber.onSuccess(it)
} ?: subscriber.onError(IllegalStateException())
The problem is the last line. When this network call fails and the response is null, the IllegalStateException will be propagated and crash the app, instead of being silently caught by onErrorReturnItem(emptyList()).
Is there any reason for this? Am I misunderstanding the idea behind onErrorReturnItem()?