I am using RxJava in which I want to dynamically create a number of Observables based on some condition. Once I'm done with creating, I want to do some processing on the different values returned by the observables and then send as a single Observable to which I can subscribe on. Here is how my code is :
List<String> valueList = ....
List<Observable<String>> listOfObservables = new ArrayList<Observable<String>>();
for(int i =; i <valueList.size(); i++){
listOfObservables.add(new SomeClass.doOperation(valueList(i)));
// SomeClass.doOperation will return an Observable<String>
}
return Observable.merge(listOfObservables);
But here , I want to do some operation on the values emitted by different Observables in the listOfObservable and finally return it as a single Observable<String>
Like in Observable.zip() , I can do this like
return Observable.zip(observable1, observable2, (string1, string2) -> {
// joining final string here
return string1 + string2;
But I know the number of arguments here. Please let me know how I can achieve this.
Use the zip overload that takes a variable number of arguments, it has a signature of
<R> Observable<R> zip(Iterable<? extends Observable<?>> ws,
FuncN<? extends R> zipFunction)
Example usage:
List<String> valueList = ....
return Observable.from(valueList)
.map(string -> SomeClass.doOperationThatReturnsObservable(string))
.toList()
.flatMap(listOfObs -> Observable.zip(listOfObs, (Object[] results) -> {
// do something with the strings in the array.
return Arrays.stream(results)
.map(Object::toString)
.collect(Collectors.joining(","));
}));
Related
There's a Mono<A> and Flux<B>, and we need to create a flux of tuples like this:
Mono<A> monoA = createMono(); // {a}
Flux<B> fluxB = createFlux(); // {b1, b2, ... b100, ...}
Flux<Tuple<A,B>> zippedTuples = magicZip(monoA, fluxB); // { (a:b1), (a:b2), ... (a:b100), ...}
What is the proper (or standard) way to write the magicZip function?
You can create this method:
private <T>Flux<Tuple2<T, T>> magicZip(Mono<T> mono, Flux<T> flux) {
Flux<T> repeatableMono = mono.repeat();
return flux.zipWith(repeatableMono);
}
Example for the String type:
Flux<Tuple2<String, String>> test = magicZip(getMono(), getFlux()).doOnNext(objects -> System.out.println(objects.getT1() + objects.getT2()));
test.blockLast();
I think that with zip function is not possible because it produces as many elements as the smallest of two.
The way I think you can achive this is:
Flux<A> fluxA = monoA.flux();
Flux<Tuple2<A,B>> zippedTuples =fluxB.flatMap(b -> fluxA.map(a -> Tuples.of(a,b)));
I want to split a flux into two fluxes where the first one has the first item of the original flux and the second one will takes the rest of items.
After applying a custom transformation myLogic on each flux I want to combine them into one flux preserving the order of the original flux.
Example:
S: student
S': student after applying myLogic
Emitted flux: s1 -> s2 -> s3 -> s4
The first splited flux: s1' => myLogic
The second splited flux: s2' -> s3' -> s4' => myLogic
The combined flux: s1' -> s2' -> s3' -> s4'
It is enough to use standard Flux methods take and skip to seprate head and tail elements. Calling cache before that is also useful to avoid subscription duplication.
class Util {
static <T, V> Flux<V> dualTransform(
Flux<T> originalFlux,
int cutpointIndex,
Function<T, V> transformHead,
Function<T, V> transformTail
) {
var cached = originalFlux.cache();
var head = cached.take(cutpointIndex).map(transformHead);
var tail = cached.skip(cutpointIndex).map(transformTail);
return Flux.concat(head, tail);
}
static void test() {
var sample = Flux.just("a", "b", "c", "d");
var result = dualTransform(
sample,
1,
x -> "{" + x.toUpperCase() + "}",
x -> "(" + x + ")"
);
result.doOnNext(System.out::print).subscribe();
// prints: {A}(b)(c)(d)
}
}
There's a more simple solution to your problem. You don't need to split and merge the events from publisher. You can make use of index(). It keeps information about the order in which events are published.
Flux<String> values = Flux.just("s1", "s2", "s3");
values.index((i, v) -> {
if (i == 0) {
return v.toUpperCase();
} else {
return v.toLowerCase();
}
});
Here's a hacky way to do this:
boolean a[] = new boolean[]{false}; //use an array as you cannot use non-final variables inside lambdas
originalFlux
.flatMap(a -> {
if(!a[0]) {
a[0] = true;
return runLogicForFirst(a);
} else {
return runLogicForRest(a);
}
})
Instead of creating two separate Flux objects and then merging them, you can just zip your original Flux with another Flux<Boolean> that's only ever true on the first element.
You can then do your processing conditionally as you please in a normal map() call without having to merge separate publishers later on:
Flux<String> values = Flux.just("A", "B", "C", "D", "E", "F", "G");
Flux.zip(Flux.concat(Flux.just(true), Flux.just(false).repeat()), values)
.map(x -> x.getT1() ? "_"+x.getT2().toUpperCase()+"_" : x.getT2().toLowerCase())
.subscribe(System.out::print); // prints "_A_bcdefg"
How to combine multiple results emmited by observables into one result and emit it once?
I have a Retrofit service:
public interface MyService {
#GET("url")
Observable<UserPostsResult> getUserPosts(#Query("userId") int id);
#GET("url")
Observable<UserPostsResult> getUserPosts(#Query("userId") int id, #Query("page") int pageId);
}
And I have a model:
public class UserPostsResult {
#SerializedName("posts")
List<UserPost> mPosts;
#SerializedName("nextPage")
int mPageId;
}
Also I have ids List<Integer> friendsIds;
My goal is to have a method like this one:
public Observable<Feed> /*or Single<Feed>*/ getFeed(List<Integer> ids) {
...
}
It returns one Observable or Single that does the following:
Combines all getUserPosts(idFromList) to one observable
For each UserPostsResult must do:
if (userPostResult.mPageId > -1)
getUserPosts(currentUserId, userPostResult.mPageId);
And merge this result to the previous userPostResult
Return one single model as result of all operations.
Result class:
public class Feed {
List<UserPost> mAllPostsForEachUser;
}
EDIT (More details):
My client specifications was that I must take from social network user posts with no logging in, no token requesting. So I must parse HTML pages. That's why I have this complex structure.
EDIT (Partial solution)
public Single<List<Post>> getFeed(List<User> users) {
return Observable.fromIterable(users)
.flatMap(user-> mService.getUserPosts(user.getId())
.flatMap(Observable::fromIterable))
.toList()
.doOnSuccess(list -> Collections.sort(list, (o1, o2) ->
Long.compare(o1.getTimestamp(), o2.getTimestamp())
));
}
This solution doesn't include pages problem. Thats why it is only partial solution
There are a number of operators which transform things into other things. fromIterable() will emit each item in the iterable, and flatMap() will convert one type of observable into another type of observable and emit those results.
Observable.fromIterable( friendsIds )
.flatMap( id -> getUserPosts( id ) )
.flatMap( userPostResult -> userPostResult.mPageId
? getUserPosts(currentUserId, userPostResult.mPageId)
: Observable.empty() )
.toList()
.subscribe( posts -> mAllPostsForEachUser = posts);
If you need join two response in one you should use Single.zip
Single.zip(firsSingle.execute(inputParams), secondSingle.execute(inputPrams),
BiFunction<FirstResponse, SecondResponse, ResponseEmitted> { firstResponse, secondResponse ->
//here you put your code
return responseEmmitted
}
}).subscribe({ response -> },{ })
I have a stream and want to apply a method only if a predicate matches.
E.g. I want to process a stream and replace all nulls by a default value. What is the best way to accomplish this?
You should just use a map value
data.stream()
.map(v -> v == null ? defaultValue : v)
... // do whatever you need to do with it.
EDIT
If you need to do this a lot you could create a Function to do it for you.
public class DefaultValue<T> extends Function<T, T> P{
private final T t;
public DefaultValue(T t){
this.t. = t;
}
public T apply(T t) {
return t == null ? this.t : t;
}
}
data.stream()
.map(new DefaultValue(someValue));
// Do what you need to do
If you are looking to preserve the original values for items that do not match your filter, use map with ternary logic:
Items that do not pass filter are returned as-is
Items that pass the filter get transformed
Here is an example:
Stream<String> stream = Arrays.stream(
new String[]{"quick", null, "brown", "fox", null, "jumps"}
);
List<String> res = stream
.map(s -> s != null ? s : "<EMPTY>")
.collect(Collectors.toList());
for (String s : res) {
System.out.println(s);
}
Filtering logic is embedded in the conditional expression inside map:
s -> s != null ? s : "<EMPTY>" // Using default values for null strings
Demo.
I'm in a bit of confusion right now, so I have a method that should return CompletableFuture<List<A>>
inside the method is:
CompletableFuture<List<String>> toReturn = asyncCall().thenApply(....)
.thenCompose(listOfStuff -> convertToList(listOfStuff.stream().map(
key -> asyncCall2(key)
.thenApply(optionalValue -> optionalValue.orElse(null))
).collect(Collectors.toList()));
and convertToList() simply joins futures to convert CompletableFuture<List<ComputableFuture<A>>> into CompletableFuture<List<A>>
Basically my intention is to filter null values that emerge from optionalValue.orElse(null) And it would be easy to do filter before collecting it all to list in the last line, but if I use it just before .collect it is working over CompletableFutures
I suspect there's a lot of restructuring I can do in my code.
EDIT:
private<T> CompletableFuture<List<T>> convertToList(List<CompletableFuture<T>> toConvert) {
return CompletableFuture.allOf(toConvert.toArray(new CompletableFuture[toConvert.size()]))
.thenApply(v -> toConvert.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
The best way would probably be to change convertToList() so that it does not return a future of list, but of stream instead:
private <T> CompletableFuture<Stream<T>> convertToFutureOfStream(List<CompletableFuture<T>> toConvert) {
return CompletableFuture.allOf(toConvert.stream().toArray(CompletableFuture[]::new))
.thenApply(
v -> toConvert.stream()
.map(CompletableFuture::join)
);
}
This will be more reusable as the method will allow better chaining and will not force the caller to work with a list, while still allowing to easily get a list with a simple collect.
You can then simply filter that stream to remove empty optionals:
CompletableFuture<List<String>> toReturn = asyncCall()
.thenCompose(listOfStuff -> convertToFutureOfStream(
listOfStuff.stream()
.map(this::asyncCall2)
.collect(Collectors.toList())
)
.thenApply(stream ->
stream.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);
You can even improve this a little further by changing convertToFutureOfStream() to take a stream as argument as well:
private <T> CompletableFuture<Stream<T>> convertToFutureOfStream(Stream<CompletableFuture<T>> stream) {
CompletableFuture<T>[] futures = stream.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(futures)
.thenApply(v -> Arrays.stream(futures).map(CompletableFuture::join));
}
(unfortunately this raises an unchecked assignment warning because of the array of generic types)
Which then gives
CompletableFuture<List<String>> toReturn = asyncCall()
.thenCompose(listOfStuff -> convertToFutureOfStream(
listOfStuff.stream().map(this::asyncCall2)
)
.thenApply(stream ->
stream.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);