Given I subscribe to 2 different Observables and I want get both of them on onNext after do some operations to them
let's say I have 2 Observables
Observable<List<String>> childName = Observable.from(children)... some operations
Observable<List<String>> teacherName = Observable.from(teachers)... some operations
how do I get both of them on my subscribe?
subscribe(
onNext(List<String> childName, List<String> className)
so that I can pass both of them in my listener in that manner.
I don't want to combine them, I just want when after the operations are finished, get both of them and pass it on my subscriptions
You can zip their values into a Pair:
Observable.zip(childName, className,
(a, b) -> Pair.of(a, b))
.subscribe((Pair<List<String>, List<String>> pair) -> {
// do something with pair.first and pair.second
}, Throwable::printStackTrace);
hacky but simple
Observable.zip(childName, teacherName, (childList, teachersList) -> {
// handle childList & teachersList
return Observable.empty();
}).subscribe(o -> {}, error -> {
//handle errors
});
Related
Java version : 11
I have a List, which contains many sublist and for each sublist I want to perform certain transformation/operations.
I want to perform this operation in non-blocking asynchronous fashion, so I am using CompletableFuture.
This is my operation:
public static List<String> convertBusinessObjectJson(List<BusinessObject> businessObjList) {
List<Either> eitherValueOrException = {//omitted logic to convert to json}
return eitherValueOrException;
}
It returns a List of Either Objects, where Either holds, either runtime exception thrown by conversion logic or String result when conversion is successful.
This is my caller code:
mainList.forEach(sublist -> {
CompletableFuture<List<Either>> listCompletableFuture = CompletableFuture.supplyAsync(() -> FutureImpl.convertBusinessObjectJson(sublist));
});
Once the CompletableFuture<List<Either>> listCompletableFuture is received, I want to chain the operation,
As in
take CompletableFuture<List<Either>> listCompletableFuture, take exceptions only from list and, perform certain operation
take CompletableFuture<List<Either>> listCompletableFuture, take results only from list and, perform certain operation
Something like this (pseudo code):
mainList.forEach(sublist -> {
CompletableFuture<List<Either>> listCompletableFuture = CompletableFuture.supplyAsync(() -> FutureImpl.convertDSRowToJson(subDSRowList));
listCompletableFuture.thenApply(//function which pushes exception to say kafka)
listCompletableFuture.thenApply(//function which pushes result to say database)
});
Can it be done?
Any help is much appreciated :)
You could try smth like this:
var futureList = mainList.stream()
.map(sublist -> CompletableFuture.supplyAsync(() -> FutureImpl.convertBusinessObjectJson(sublist)))
.collect(Collectors.toList());
The above would collect a list of CompletableFutures. Now what needs to happen is we need to wait for the completion of all those futures. We do this by:
var joinedFutureList = futureList.stream()
.map(objectCompletableFuture -> {
try {
return objectCompletableFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
});
After that the separation would look smth like this:
var exceptionList = joinedFutureList.stream()
.filter(obj -> obj instanceof Exception)
.peek(System.out::println)
.collect(Collectors.toList());
var successList = joinedFutureList.stream()
.filter(obj -> obj instanceof String)
.peek(System.out::println)
.collect(Collectors.toList());
I want to know if there's an appropriate RxJava operator for my use-case. I have the below two methods. These are retrofit interfaces.
fun getSources(): Single<Sources>
fun getTopHeadlines(sourceCsv: String): Single<TopHeadlines>
Currently I'm doing this
getSources()
.map { sources ->
// convert the sources list to a csv of string
}
.flatMap { sourcesCsv
getTopHeadlines(sourcesCsv)
}
.subsribe {topHeadlines, error -> }
This works well and good, if my objective is to get the top headlines. But what I'm instead trying to get is both the sources as well as top headlines while subscribing to it? Is there any operator that I'm not aware off or is there any other way of doing the same?
you can use the zip() method to do this. zip waits for both items then emits the required value. you can use it like this
getSources()
.map { sources ->
// convert the sources list to a csv of string
}
.flatMap { sourcesCsv ->
Single.zip(
Single.just(sourcesCsv),
getTopHeadlines(sourcesCsv),
BiFunction { t1, t2 -> Pair(t1, t2) }
)
}
then when you subscribe to this you have both values as a Pair. you can make an extension function for it and make you life easier:
fun <T, V> Single<T>.zipWithValue(value: V) = Single.zip(
Single.just(value),
this,
{ t1, t2 -> Pair(t1, t2) }
)
and inside your flatMap you can do getTopHeadlines(sourcesCsv).zipWithValue(sourcesCsv). the same could be done for Maybe, and for Flowabale you can use combineLatest() method.
As an addition to mohsens answer:
You do not need to zip it. Just use another map operator inside the flatMap and combine both values to a Pair, just as I did in this example:
import io.reactivex.rxjava3.core.Single
import org.junit.jupiter.api.Test
class So65640603 {
#Test
fun `65640603`() {
getSources()
.flatMap { source -> getTopHeadlines(source).map { headLines -> source to headLines } }
.test()
.assertValue("v1" to 42)
}
}
fun getSources(): Single<String> {
return Single.just("v1")
}
fun getTopHeadlines(sourceCsv: String): Single<Int> {
return Single.just(42)
}
I have multiple observables doing network calls and I need to emit error from combiner observable only when all observables emitted error. If at least one observable completes then the result should be passed.
My current stream functions like this:
Observable.fromIterable(list)
.flatMap{networkObservable}
.reduce{combine result of all network observables to same list}
I could just do:
Observable.fromIterable(list)
.flatMap{networkObservable.onErrorReturnItem(errorItem)}
.reduce{check if all items are error items and return error item otherwise combine result of
all network observables to same list}
But I wanted to know if there's a better way to handle this case.
If I understand correctly, if there is at least one item from any of the networkObservables, errors should be ignored, right? In this case, you could detect the onNexts emitted, then in the error handler, suppress the error:
Observable.using(
() -> new AtomicBoolean(),
hasItem -> Observable.fromIterable(list)
.flatMap(v -> networkObservable, true) // delay errors
.doOnNext(v -> {
if (!hasItem.get()) {
hasItem.set(true);
}
})
.onErrorResumeNext(error -> {
if (hasItem.get()) {
return Observable.empty();
}
return Observable.error(error);
}),
() -> { } // no need to clean up the AtomicBoolean
)
We have an async method:
public CompletableFuture<OlderCat> asyncGetOlderCat(String catName)
Given a list of Cats:
List<Cat> cats;
We like to create a bulk operation that will result in a map between the cat name and its async result:
public CompletableFuture<Map<String, OlderCat>>
We also like that if an exception was thrown from the asyncGetOlderCat, the cat will not be added to the map.
We were following this post and also this one and we came up with this code:
List<Cat> cats = ...
Map<String, CompletableFuture<OlderCat>> completableFutures = cats
.stream()
.collect(Collectors.toMap(Cat::getName,
c -> asynceGetOlderCat(c.getName())
.exceptionally( ex -> /* null?? */ ))
));
CompletableFuture<Void> allFutures = CompletableFuture
.allOf(completableFutures.values().toArray(new CompletableFuture[completableFutures.size()]));
return allFutures.thenApply(future -> completableFutures.keySet().stream()
.map(CompletableFuture::join) ???
.collect(Collectors.toMap(????)));
But it is not clear how in the allFutureswe can get access to the cat name and how to match between the OlderCat & the catName.
Can it be achieved?
You are almost there. You don't need to put an exceptionally() on the initial futures, but you should use handle() instead of thenApply() after the allOf(), because if any future fails, the allOf() will fail as well.
When processing the futures, you can then just filter out the failing ones from the result, and rebuild the expected map:
Map<String, CompletableFuture<OlderCat>> completableFutures = cats
.stream()
.collect(toMap(Cat::getName, c -> asyncGetOlderCat(c.getName())));
CompletableFuture<Void> allFutures = CompletableFuture
.allOf(completableFutures.values().toArray(new CompletableFuture[0]));
return allFutures.handle((dummy, ex) ->
completableFutures.entrySet().stream()
.filter(entry -> !entry.getValue().isCompletedExceptionally())
.collect(toMap(Map.Entry::getKey, e -> e.getValue().join())));
Note that the calls to join() are guaranteed to be non-blocking since the thenApply() will only be executed after all futures are completed.
As I get it, what you need is CompletableFuture with all results, the code below does exactly what you need
public CompletableFuture<Map<String, OlderCat>> getOlderCats(List<Cat> cats) {
return CompletableFuture.supplyAsync(
() -> {
Map<String, CompletableFuture<OlderCat>> completableFutures = cats
.stream()
.collect(Collectors.toMap(Cat::getName,
c -> asyncGetOlderCat(c.getName())
.exceptionally(ex -> {
ex.printStackTrace();
// if exception happens - return null
// if you don't want null - save failed ones to separate list and process them separately
return null;
}))
);
return completableFutures
.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().join()
));
}
);
}
What it does here - returns future, which creates more completable future inside and waits at the end.
I have a very large list and want to perform a simple Filter operation on it in a background thread, and then get the list of results that match the filter criteria at the end of the operation.
I'm still new at RxJava so I'm struggling to get this to work. What I have is the following (largeList is an ArrayList filled with Item):
Observable
.fromIterable(largeList)
.filter { it.name.contains(query) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { ... }
When I subscribe, I get back a single Item instead of a complete list of all items which passed the filter. How can I create a sublist of all matched results?
Use the toList() operator.
For a given Observable, it will collect incoming emissions into a List and then push that entire List as a single emission (through Single>).
Observable
.fromIterable(largeList)
.filter { it.name.contains(query) }
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { ... }
Also, you can optionally specify an integer argument to serve as the capacityHint, and that will optimize the initialization of ArrayList to expect roughly that number of items:
Observable
.fromIterable(largeList)
.filter { it.name.contains(query) }
.toList(capacityHint)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { ... }