Retrofit + rxJava: how to implement iterable N requests? - java

I have a problem to implement following problem: I'm making a request that fetches all active leagues. Then, for each of them I need to make another request to grab the matches. I think it's possible to implement the solution using flatMapIterable, but don't know how.
For now I have following retrofit interfaces:
public interface LeaguesApi
{
#GET("/api/get-leagues")
Observable<ArrayList<League>> getLeagues(#Query("active_only") boolean activeOnly);
}
public interface LeagueApi
{
#GET("/api/get-league-fixtures/{leagueId}")
Observable<ArrayList<Match>> getMatchesPlayed(#Path("leagueId") int leagueId, #Query("played") boolean played);
}
Please advise how to iterate through all leagues in order to perform getMatchesPlayed for each of them. Best would be without lambda expressions, since I'm not using them in my project.

Try this code:
leaguesApi // your REST adapter from retrofit
.getLeagues(true) // fetch leagues
.flatMapIterable(leagues -> leagues) //Transform from ArrayList<Liague> to Observable<Liague>
.flatMap(l -> leagueApi.getMatchesPlayed(l.getId(), true))
.subscribe(
(match) -> {/*This is onNext*/},
t -> t.printStackTrace(), //onError
() -> {/*onComplete*/}
);
UPDATE:
Without lambdas:
leaguesApi // your REST adapter from retrofit
.getLeagues(true) // fetch leagues
.flatMapIterable(new Func1<ArrayList<League>, Iterable<?>>() {
#Override
public Iterable<?> call(ArrayList<League> leagues) {
return leagues;
}
}) //Transform from ArrayList<Liague> to Observable<Liague>
.flatMap(new Func1<League, Observable<Match>>() {
#Override
public Observable<Match> call(League l) {
return leagueApi.getMatchesPlayed(l.getId(), true);
}
})
.subscribe(
new Action1<Match>() {
#Override
public void call(Match match) {
//onNext
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
//onError
}
},
new Action0() {
#Override
public void call() {
//onComplete
}
}
);

I'd change that API so it reads like this otherwise you lose a lot of the flexibility of streams:
public interface LeaguesApi
{
#GET("/api/get-leagues")
Observable<League> getLeagues(#Query("active_only") boolean activeOnly);
}
public interface LeagueApi
{
#GET("/api/get-league-fixtures/{leagueId}")
Observable<Match> getMatchesPlayed(#Path("leagueId") int leagueId, #Query("played") boolean played);
}
Can you do that?
If not then to get Observable<T> from Observable<ArrayList<T>> you do:
observable.flatMapIterable(
new Func1<ArrayList<T>, ArrayList<T>>() {
#Override
public ArrayList<T> call(ArrayList<T> list) {
return list;
}
});
much nicer to just say observable.flatMapIterable(x -> x) of course.
To get all played matches for all active leagues just do this:
Observable<League> leagues= getLeagues(true);
Observable<Match> matches =
leagues.flatMap( league -> getMatchesPlayed(league.leagueId, true));
or without lambdas (I wish you hadn't asked for that)
Observable<Match> matches = leagues.flatMap(
new Func1<League, Observable<Match>> () {
#Override
public Observable<Match> call(League league) {
return getMatchesPlayed(league.leagueId, true);
}
});

Related

rxjava chain observalbes dynamiclly

I have a chained observable that I created like this:
Disposable disposable = currentUsedAdapter.connect(ip)
.observeOn(AndroidSchedulers.mainThread())
.concatMap(fallbackAdapter(ProtocolType.V2))
.delay(500, TimeUnit.MILLISECONDS)
.concatMap(fallbackAdapter(ProtocolType.V1))
.subscribeWith(connectionSubscriber);
and this is the method fallbackAdapter:
private Function<Boolean, Observable<Boolean>> fallbackAdapter(ProtocolType protocolType) {
return new Function<Boolean, Observable<Boolean>>() {
#Override
public Observable<Boolean> apply(#NonNull Boolean isConnected) throws Exception {
if (isConnected) {
return Observable.just(true);
} else {
TempAdapter adapter = new TempAdapter(context, protocolType);
return currentUsedAdapter.connect(ipAddress);
}
}
};
}
currently this is done staticlly, and it's working fine.
Though I want to create a list of those fallbackAdapter(ProtocolType.*) because I only know the amount of fallbacks during runtime.
So I created this:
ArrayList<Function<Boolean, Observable<Boolean>>> adaptersList = new ArrayList<>();
adaptersList.add(fallbackAdapter(ProtocolType.V2));
...
adaptersList.add(fallbackAdapter(ProtocolType.V9));
Disposable disposable = Observable.fromIterable(adaptersList)
.concatMap(adapter ->
adapter.apply(true))
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(connectionSubscriber);
I have created a list that I can update dynamiclly.
BUT, I am not sure how I can pass the value of isConnected from one adapter to another. I currently pass true to everyone, but some of them should return false, but I'm not sure how I can pass this value from one emitter to another using the Observable.fromIterable.
So my question is how should I change this .concatMap(adapter -> adapter.apply(true)) so that I won't always send true but rather I will send the value that been processed by the previous adapter?
thank you
If it helps anyone...
I didn't find an rxjava way to solve it so I solved in an on old java fashion way...
I have created a builder class and added an observable to my main observable and at the end I returned everything.
something like that:
public class DisposableBuilder {
Observable<Boolean> observable;
public DisposableBuilder() {
}
public void build(String ip) {
observable = currentUsedAdapter.connect(host);
if (adaptersNames != null) {
for (int i = 1; i < adaptersNames.size(); i++) { // skip first adapter (currentUsedAdapter adapter)
this.append(AdapterFactory.getAdapter(context, adaptersNames.get(i)));
}
}
}
public void append(CustomAdapter adapter) {
observable = observable
.delay(200, TimeUnit.MILLISECONDS)
.concatMap(fallbackAdapter(adapter));
}
public Observable<Boolean> getObservable() {
return observable;
}
}
and then I used it like so:
disposableBuilder.build(ip);
this.disposable = disposableBuilder.getObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(connectionSubscriber);

How to emit whole list only once even if range() operator iterate itself corresponding to range passed in it

Problem
I am having a list which is coming from the backend after calling some API, I need to convert that list into some other type of list. Afterward, I need to add n number of an empty object into the final list. (n number is coming from some logic which suits my application.)
To solve this problem I have written the following code :
Observable.fromIterable(snipList).map(new Function<UserSnipModel, TaskContent>() {
#Override
public TaskContent apply(UserSnipModel userSnipModel) throws Exception {
String displayName = userSnipModel.getDisplayName();
TaskContent.Fields fields = new TaskContent.Fields(userSnipModel.getId(),
displayName, userSnipModel.getUrlCall(), userSnipModel.getRemarks(), userSnipModel.isEnableCall());
Drawable icon = DrawableUtils.getDrawable(HomeActivity.this, userSnipModel.getIconName(), R.drawable.ic_diamond);
return new TaskContent(fields, icon);
}
}).toList().toObservable().flatMap(new Function<List<TaskContent>, ObservableSource<List<TaskContent>>>() {
#Override
public ObservableSource<List<TaskContent>> apply(List<TaskContent> taskContents) throws Exception {
return Observable.range(0, totalNumberOfMembers - numberOfItems).map(new Function<Integer, List<TaskContent>>() {
#Override
public List<TaskContent> apply(Integer integer) throws Exception {
taskContents.add(new TaskContent(new TaskContent.Fields("",
"", "", "", false), null));
return taskContents;
}
});
}
})
.observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).safeSubscribe(new Observer<ListTaskContent>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<TaskContent> taskContents) {
//This method is called multiple time.
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onComplete() {
}
});
When I run this, onNext() is called multiple time which is not working as expected. To handle this problem I added toList() inside the flatMap() then I got List<List<TastContent>> which is also not expected as I need only List<TaskContent>
What can I do to handle this problem?
Thanks in advance.
I'd suggest this solution:
Observable.fromIterable(list)
.map(item -> item.concat("new")) //Converting the items in the initial list
.toList()
.flatMap(data -> Single.zip(
Single.just(data), //Mapped data set
Observable.range(0, data.size()) //Empty list generator
.map(rangeVal -> rangeVal.toString())
.toList(),
(initialList, emptyList) -> {
initialList.addAll(emptyList); //Appending lists
return initialList;
}
)
)
.subscribe(data -> {}, error -> {});

RxJava - How to get Single from nested method

In my Presenter i have a method which gets some list from DataHolder:
disposable.add(dataHolder.getMonthOfAttractions(monthInAdvance)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<Map<String, List<Attraction>>>() {
#Override
public void onSuccess(Map<String, List<Attraction>> stringListMap) {
}
#Override
public void onError(Throwable e) {
}
}));
Then, in my DataHolder I'm checking if my list isn't null. If true, returns my list, if false it downloads this list from server :
public Single<Map<String, List<Attraction>>> getMonthOfAttractions(int monthInAdvance) {
Map<String, List<Attraction>> monthOfAttractions = monthlyAttractionsMap.get(monthInAdvance);
if (monthOfAttractions != null)
return Single.fromCallable(() -> monthOfAttractions);
else
return apiGetMonthOfAttractions(monthInAdvance);
The problem is with apiGetMonthOfAttractions method. I dont know how to correctly implement this method to return value to my Presenter.
I've tried something like:
private Single<Map<String, List<Attraction>>> apiGetMonthOfAttractions(int monthInAdvance) {
cnkRetrofitProvider.getApiInterface().getAttractions(monthInAdvance)
.subscribeWith(new CnkApiObserver<AttractionListResponse>() {
#Override
public void onSucceeded(AttractionListResponse result) {
result.getMonthOfAttractions();
}
#Override
public void onFailed(Error error) {
}
});
}
But in this case i have "missing return statement" and I'm out of ideas how to implement it. I'm begging to learn RxJava, so be understanding.
Please help :)
EDIT:
This is what how my Retrofit getAttractions() method looks like:
public interface CnkApiInterface {
#GET("pl/front-api/{dateFrom}/{dateTo}")
Single<AttractionListResponse> getAttractions(#Path("dateFrom") String dateFrom, #Path("dateTo") String dateTo);}
This is what you are after:
private Single<Map<String, List<Attraction>>> apiGetMonthOfAttractions(int monthInAdvance) {
return cnkRetrofitProvider.getApiInterface()
.getAttractions(monthInAdvance)
.flatMap(attractionListResponse -> Single.just(attractionListResponse.getMonthOfAttractions()));
}
I thing just try to do something like (it only depends what does your cnkRetrofitProvider.getApiInterface().getAttractions(monthInAdvance) returns)
private Single<Map<String, List<Attraction>>> apiGetMonthOfAttractions(int monthInAdvance) {
return cnkRetrofitProvider.getApiInterface().getAttractions(monthInAdvance)
}
should do the trick
You can always just map the result to List<Attraction> so #wojech_maciejewski s answer still holds, you jast need to add a mapping function.
private Single<Map<String, List<Attraction>>> apiGetMonthOfAttractions(int monthInAdvance) {
return cnkRetrofitProvider.getApiInterface().getAttractions(monthInAdvance)
.map(atractions -> /* convert to List<Attraction> here */)
}

How to store stream result into multiple lists or maps

Is there any better way to do this? Note that the code below is working.
List<Object1> details1 = new ArrayList<>();
List<Object2> details2 = new ArrayList<>();
List<Object3> details3 = new ArrayList<>();
endPointList.parallelStream().forEach(endPoint -> {
details1.addAll(ConfigHelper.getConfig1(endPoint));
details2.addAll(ConfigHelper.getConfig2(endPoint));
details3.addAll(ConfigHelper.getConfig3(endPoint));
});
I do not think that there is a built-in way to do this. But if you have to do it very often, it might be worth writing your own Collector. This will not make it shorter, but it will look more like other collectors when using it.
public static class ConfigCollector implements Collector<EndPoint, ConfigCollector, ConfigCollector>
{
List<Config1> config1s = new CopyOnWriteArrayList<>();
List<Config2> config2s = new CopyOnWriteArrayList<>();
List<Config3> config3s = new CopyOnWriteArrayList<>();
public List<Config1> getConfig1s() { return config1s; }
public List<Config2> getConfig2s() { return config2s; }
public List<Config3> getConfig3s() { return config3s; }
#Override
public BiConsumer<ConfigCollector, EndPoint> accumulator()
{
return (cc, e) -> {
cc.config1s.addAll(ConfigHelper.getConfig1(endPoint));
cc.config2s.addAll(ConfigHelper.getConfig2(endPoint));
cc.config3s.addAll(ConfigHelper.getConfig3(endPoint));
};
}
#Override
public Set<java.util.stream.Collector.Characteristics> characteristics()
{
HashSet<java.util.stream.Collector.Characteristics> set = new HashSet<>();
set.add(Characteristics.IDENTITY_FINISH);
set.add(Characteristics.UNORDERED);
set.add(Characteristics.CONCURRENT);
return set;
}
#Override
public BinaryOperator<ConfigCollector> combiner()
{
return (cc1, cc2) -> {
cc1.config1s.addAll(cc2.config1s);
cc1.config2s.addAll(cc2.config2s);
cc1.config3s.addAll(cc2.config3s);
return cc1;
};
}
#Override
public Function<ConfigCollector, ConfigCollector> finisher()
{
return Function.identity();
}
#Override
public Supplier<ConfigCollector> supplier()
{
return ConfigCollector::new;
}
}
Using it like this:
ConfigCollector configs = endPointList.parallelStream().collect(new ConfigCollector());
configs.getConfig1s();
configs.getConfig2s();
configs.getConfig3s();
This will make it easier to deal with what the other commenters mentioned about concurrency (as you deal with it inside the collector instead of inside your business code). Maybe you have not run into this issue because your stream is small enough so that it has not done anything in paralllel yet.

RxJava sewing two queries

How to sew two Observable in RxJava ?
Observable<List<CalendarEvent>>, for each CalendarEvent, I want to do network operation to read the (lat,lon) and fetch place name, and then combine place name back to CalendarEvent.
public Observable<List<CalendarEvent>> getEvents() {
// get events
// translate each Event LatLng to Place and bind it to Event
// return the events
}
public Observable<List<CalendarEvent>> getEvents() {
List<CalendarEvent> sourceList = ...
return Observable.from(sourceList) //emits each item separately
.concatMap(calendarEvent -> applyPlaceName(calendarEvent)) //fetches places and applies them for each item
//fyi: concatMap executes requests sequentially, if you want do it in parallel - use flatMap instead
.toList(); //collects items to list
}
//somewhere in your Networking class
public Observable<CalendarEvent> applyPlaceName(CalendarEvent calendarEvent) {
return Observable ... //do network call and apply placeName on item
}
//p.s. don't forget to apply appropriate Schedulers
No need for something fancy here, this would roughly do what you want I think:
public class Foobar {
void doSomethingWithEvents() {
getEvents().subscribe(new Action1<List<CalendarEvent>>() {
#Override
public void call(List<CalendarEvent> calendarEvents) {
for (CalendarEvent event : calendarEvents) {
getPlaceForEvent(event).subscribe(new Action1<Place>() {
#Override
public void call(Place place) {
event.setPlace(place);
}
});
}
}
});
}
Observable<Place> getPlaceForEvent(CalendarEvent event) {
return Observable.just(new Place());
}
Observable<List<CalendarEvent>> getEvents() {
return Observable.just(new ArrayList<CalendarEvent>());
}
}

Categories

Resources