Chain two answers Single - java

I need to link two RX Single responses - Retrofit to get an ArrayList with the two responses that return a List
I tried to process both answers with Map, Flatmap, but I have not achieved what I expected
final ArrayList <List<Consent>> listAllConsents = new ArrayList<>();
Single<List<Consent>> responseDspConsent = subscriptionCenterRemoteDataSource.getConsents(Globals.getAuthorizationTokenUser());
Single<List<Consent>> responseDspConsentByApp = subscriptionCenterRemoteDataSource.getConsentsByApp(Globals.getAuthorizationTokenUser());
responseDspConsentByApp.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
responseDspConsent.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(consentData -> {
List<Consent> consentList = consentData;
listAllConsents.add(consentList);
return responseDspConsentByApp.map(consentDataByApp -> {
List<Consent> consentListByApp = consentDataByApp;
listAllConsents.add(consentListByApp);
return listAllConsents;
});
})
.subscribe(consentData -> {
Log.v("Entramoss", "Valor: " + listAllConsents.get(0).get(0).getTitle());
paintAllConsents(listAllConsents);
});
I need to have all the objects of the two responses in the arrayList so I can paint them later.

You have 2 ways to do this.
1.You can use Observable.concat(Obs 1, Obs 2). The concat operator concatenates the observables and returns a single observable which first emits the items from the first observable then the second one. Source: http://reactivex.io/documentation/operators/concat.html
Single<List<Consent>> responseDspConsent = subscriptionCenterRemoteDataSource
.getConsents(Globals.getAuthorizationTokenUser())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Single<List<Consent>> responseDspConsentByApp = subscriptionCenterRemoteDataSource
.getConsentsByApp(Globals.getAuthorizationTokenUser())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable.concat(responseDspConsent.toObservable(),responseDspConsentByApp.toObservable())
.toList()
.doOnSuccess((list) -> {
paintAllConsents(list);
})
.subscribe();
2.You can use the .concatWith operator which does the same thing as the concat operator but now it concats an observable to another without creating a new observable.
Single<List<Consent>> responseDspConsent = subscriptionCenterRemoteDataSource
.getConsents(Globals.getAuthorizationTokenUser())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Single<List<Consent>> responseDspConsentByApp = subscriptionCenterRemoteDataSource
.getConsentsByApp(Globals.getAuthorizationTokenUser())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
responseDspConsent.concatWith(responseDspConsentByApp)
.toList()
.doOnSuccess((list) -> {
paintAllConsents(list);
})
.subscribe();

if your order do meter i suggest you to do .concat as #ebasha response but if your order doesn't meter i suggest you to use .merge becase is much more faster than .concat because concat subscribe streams one by one and merge subscribe streams immediately
Single<List<Consent>> responseDspConsent = subscriptionCenterRemoteDataSource
.getConsents(Globals.getAuthorizationTokenUser())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Single<List<Consent>> responseDspConsentByApp = subscriptionCenterRemoteDataSource
.getConsentsByApp(Globals.getAuthorizationTokenUser())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable.merge(responseDspConsent.toObservable(),responseDspConsentByApp.toObservable())
.toList()
.doOnSuccess((list) -> {
paintAllConsents(list);
})
.subscribe();

Related

Get Flux size when Flux is complete

I'm kinda stuck with a trivial task: whenever I query an external API with reactive spring WebClient or query reactive MongoDBRepository, I'd like to log how many entities got through my flux, eg. to log message like "Found n records in the database.". Eg:
return repository.findAll()
.doOnComplete { log.info("Found total n records!") } // how to get the n?
.filter { it.age > 10 }
.distinct { it.name }
TLDR: How to get a flux size (and perhaps it's contents) when it completes?
You can use ConnectableFlux. In your example:
var all = repository.findAll()
.filter { it.age > 10 }
.distinct { it.name }
.publish()
.autoConnect(2)
all.count()
.subscribe {c -> log.info("Found total {} records!", c)}
return all;
By calling the count(). It should emit a Mono when onComplete is observed.
Here was what I did,
AtomicInteger i = new AtomicInteger();
Flux<UserDetails> stringFlux =
Flux.using(() -> stringStream, Flux::fromStream,
Stream::close)
.doOnNext(s -> i.getAndIncrement())
.log()
.map(UserDetails::createUserDetails);
stringFlux
.subscribe(updateUserDetailsService::updateUserDetails);
log.info("number of records: {}", i);

FlatMap an iterable and combine it back into iterable

I have an API that returns a Single. This Single contains a list of values, let's say String values. When I am calling this object, I get that Single and have to filter some values from it and return back another Single. I'm trying to achieve something like in this simplified test:
#Test
public void filterTest() {
List<String> sourceList = Arrays.asList("email", "phone", "smoke", "email", "phone", "fax", "email");
Single.just(sourceList)
.toObservable()
.flatMap(source -> {
return Observable.from(source);
})
.filter(source -> !source.equals("email"))
.groupBy(/* criteria? */)
//how to extract single list from groupBy or
//is there another opposite function for flatMap?
.toSingle()
.subscribe(s -> System.out.println(s));
}
Try this:
Single.just(sourceList)
.flattenAsObservable(source -> source)
.filter(source -> !source.equals("email"))
.toList()
.subscribe(s -> System.out.println(s));
or
Observable.fromIterable(sourceList)
.filter(source -> !source.equals("email"))
.toList()
.subscribe(s -> System.out.println(s));

How to return Completable from void methods

I've got the following piece of code and it works perfectly when my inner data structure of the writerObj is CopyOnWriteArrayList (concurrent one) and it crashes when I use ArrayList.
Here're my questions:
But there's only one thread in RxJava by default, isn't it?
Will the lines (between player { ... }) execute in sync way?
My code looks as follows:
.flatMapCompletable { player -> {
writerObj.write(player); // void write(Player player) adds player to inner data structure using ds.add()
return Completable.complete();
}
}
Depends on how the rest of your chain is coded.
Have a look at the following:
List<String> writerObj = new ArrayList<>();
Observable.range(0, 1000)
.map(i -> Observable.just("hello world"))
.flatMap(obs -> obs
.flatMapCompletable(elem -> {
writerObj.add(elem);
System.out.println(Thread.currentThread().getName() + " executing");
return Completable.complete();
})
.toObservable()
.subscribeOn(Schedulers.io())
)
.blockingSubscribe();
//Size of the list is not always 1000
System.out.println("The size of the list is : " + writerObj.size());
If you execute the code above, you might notice that the size of the List at the end is not always 1000. If you change the Implementation of List to CopyOnWriteArrayList, we get the desired result.
If you want the code in the flatMap to execute sequentially and by one thread at a time, change the flatMap to concatMap.
List<String> writerObj = new ArrayList<>();
Observable.range(0, 1000)
.map(i -> Observable.just("hello world"))
.concatMap(obs -> obs
.flatMapCompletable(elem -> {
writerObj.add(elem);
System.out.println(Thread.currentThread().getName() + " executing");
return Completable.complete();
})
.toObservable()
.subscribeOn(Schedulers.io())
)
.blockingSubscribe();
// Size is always 1000
System.out.println("The size of the list is : " + writerObj.size());
Hope it helps!

Rxjava chain more than one request

I am new in concept of RxJava.
I would like to chain some calls:
Observable<RoomList> listRoomsCall = mRoomServiceApi.listRooms();
//This call will get me RoomIds
Next step is to call for all RoomIds - request after request
mMeetingServiceApi.listMeetings(roomID, startsAtString, endsAtString, free))
How should I chain first call with next calls?
I thinkt that I should use flatMap and loop to call all requets but how to connect all responses on the end?
listRoomsCall.flatMap(v -> {
for (ExchangeRoom exchangeRoom : v.getExchangeRoomList()) {
mMeetingServiceApi.listMeetings(roomID, startsAtString, endsAtString, free);
}
})
Turn the inner list into an Observable and flatMap over it again:
listRoomsCall
.flatMapIterable(v -> v.getExchangeRoomList())
.flatMap(exchangeRoom -> {
mMeetingServiceApi.listMeetings(roomID, startsAtString, endsAtString, free);
})
.subscribe(/* */);
or
listRoomsCall
.flatMap(v ->
Observable.fromIterable(v.getExchangeRoomList())
.flatMap(exchangeRoom -> {
mMeetingServiceApi.listMeetings(roomID, startsAtString, endsAtString, free);
})
)
.subscribe(/* */);

RxJava collect() & takeUntil()

I have a list of users of unknown size. What i want is to query first 30 and update UI. Then i want to query all others by offset with step of 100 until i get last pack of users - should i use takeUntil here?) and when i get - i update UI by adding remaining users (combined with reduce() i belive).
This is my code:
final int INITIAL_OFFSET = 0;
final int INITIAL_LIMIT = 30;
// Loading first 30 users to immediately update UI (better UX)
getServerApi().getAllFriends(userId, "photo_50", INITIAL_OFFSET, INITIAL_LIMIT)
// Loading remaining users 100 by 100 and updating UI after all users been loaded
.flatMap(users -> {
AtomicInteger newOffset = new AtomicInteger(INITIAL_LIMIT);
return Observable.just(users)
.flatMap(users1 -> getServerApi().getAllFriends(userId, "photo_50", newOffset.get(), Config.DEFAULT_FRIEND_REQUEST_COUNT))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.collect(() -> new ArrayList<User>(), (b, s) -> {
b.addAll(s);
newOffset.set(newOffset.get() + Config.DEFAULT_FRIEND_REQUEST_COUNT);
})
.repeat()
.takeUntil(friends -> friends.size() == 0);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(users -> getView().appendAllFriends(users),
throwable -> getView().setError(processFail(throwable, ServerApi.Action.GET_ALL_FRIENDS), false));
But seems i do something wrong because onNext is called each time the retrofit call is made.
Answering my own question. Adels answer is good, but i needed to have a single subscription (i'm using Nucleus MVP library) and i wanted to use collect() and takeUntil() instead of while loop (which requires blocking retrofit interface method).
Spent some hours and finally got it:
final int INITIAL_LIMIT = 30;
// Loading first 30 users to immediately update UI (better UX)
getServerApi().getAllFriends(userId, "photo_50", null, INITIAL_LIMIT)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// Updating UI 1st time or show error
.doOnNext(users -> getView().appendAllFriends(users))
.doOnError(throwable -> getView().setError(processFail(throwable, ServerApi.Action.GET_ALL_FRIENDS), false))
// Loading remaining users 100 by 100 and updating UI after all users been loaded
.flatMap(users -> {
AtomicInteger newOffset = new AtomicInteger(INITIAL_LIMIT);
ArrayList<User> remainingUsers = new ArrayList<>();
AtomicBoolean hasMore = new AtomicBoolean(true);
return Observable.just(users)
.flatMap(users1 -> getServerApi().getAllFriends(userId, "photo_50", newOffset.get(), Config.DEFAULT_FRIEND_REQUEST_COUNT))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.collect(() -> remainingUsers, (b, s) -> {
// Needed for takeUntil
hasMore.set(b.addAll(s));
newOffset.set(newOffset.get() + Config.DEFAULT_FRIEND_REQUEST_COUNT);
})
.repeat()
.takeUntil(friends -> !hasMore.get())
// Grab all items emitted by collect()
.last()
// Updating UI last time
.doOnNext(users2 -> getView().appendAllFriends(users2));
})
.subscribe();
Maybe it will be useful for other people which are also using Nucleus.
// cache() will ensure that we load the first pack only once
Observable<Users> firstPack = firstPack().cache();
// this subscription is for updating the UI on the first batch
firstPack
.observeOn(AndroidSchedulers.mainThread())
.subscribe(x -> draw(x), e -> whoops(e));
// this subscription is for collecting all the stuff
// do whatever tricks you need to do with your backend API to get the full list of stuff
firstPack
.flatMap(fp -> rest(fp))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(x -> allUsers(x), e -> whoops(e));
// I would do this in a simple while loop
Observable<List<User>> rest(List<User> firstPack) {
return Observable.create(sub -> {
final List<User> total = firstPack;
try {
while (!sub.isUnsubscribed()) {
final List<User> friends = api.getFriendsBlocking(total.size());
if (friends.isEmpty()) {
sub.onNext(total);
sub.onCompleted();
} else {
total.addAll(friends);
}
}
} catch(IOException e) {
sub.onError(e);
}
})
}

Categories