How to get data from more than one json api - java

I have more than one json api ex: api1 ,api2 and api3
Each one of them has different structure from the others,
I want to get data from these three api and combine them in one recycler view , i searched about these subject but couldn’t get any useful tutorial i am already using retrofit for fetch data from only one api , should i using rxjava withe retrofit to do what i want And how .

I'm guessing you're looking for the zip method. It basically takes many observables, waits for their items - all of them- to arrive and then merges them.
So you can basically do each API call in an observable, whatever they return you'll get it all and do whatever you want with them in order to prepare them to be included in your recyclerview, and finally when the whole operation succeeds you'll populate your recyclerview.
Observable obs1 = Observable.fromCallable(// Callable 1);
Observable obs2 = Observable.fromCallable(// Callable 2);
Observable obs3 = Observable.fromCallable(// Callable 3);
Observable.zip(obs1, obs2, obs3, (o1, o2, o3) -> // something)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(o -> {
// on success
});

Related

Given an existing Flux (FluxMap), how can I emit message to this Flux?

Our project use a external library. It has a method return FluxMap (since FluxMap is not completely public so just call it Flux):
Flux<MappedType> aFluxMap = Library.createMappingToMappedType();
I have to emit some objects to aFluxMap to get them converted to MappedType (it has private constructor, few setter), then I can:
aFluxMap.doOnNext(converted -> doJob(converted))
I expect that there is a method on Flux/Mono like:
aFluxMap.emit(myObj);
But I could not find any method like that.
I have searched "how to emit to flux dynamically", then there is a solution:
FluxProcessor p = UnicastProcessor.create().serialize();
FluxSink sink = p.sink();
sink.next(mess);
But seem that it emit to newly created flux (p), not my aFluxMap. So I want to ask is there any way to emit message to a existed Flux (or how to connect a FluxSink to a existed Flux, so that whenever FluxSink .next(mess), then the existed Flux get the message). Thank you
Note: please don't pay much attention to the stupidity of the library. We must use it
==========================================
UPDATE:
As #lkatiforis suggestion:
FluxProcessor p = //see above
Flux<MappedType> aFluxMap = Library.createMappingToMappedType();
p.flatMap(raw -> aFluxMap).subscribe();
I got another issue. Library.createMappingToMappedType() return a subscribed Flux with its source is UnicastProcessor (also subscribed).
When I call p.flatMap(raw -> aFluxMap), then internally aFluxMap get subscribed again cause its source also get subscribed again, so that I got an exception telling that "UnicastProcessor can be subscribe once". Any suggestion?
You can create a new stream and then merge the two streams into one by using one of these methods: merge, concat, zip, and their variants.
Here is an example:
Flux<MappedType> yourFlux = //...
Flux<MappedType> aFluxMap = Library.createMappingToMappedType();
Flux.merge(aFluxMap, yourFlux);
The merge operator executes a merging of the MappedType objects from the two provided publisher sequences.

Capture elements from beginning of flux and create a new flux that contains both captured elements and remaining elements

I'm working with a spring cloud gateway based project and my goal is to capture and log incoming and outgoing messages partially. Request logging must be done before request is passed to backend service and same policy applies to response. Implementation should be based on a filter. I have no control over when gateway subscribes to resulting flux.
In short, I would like to do following:
Capture up to x bytes of data from flux
Log captured data
Create a flux that contains both captured data and remaining data
So far I got this - and it seems to be working. I'd just like to know, if I missed something and/or if there's a better way to implement this. I'm sure someone else has been struggling with a similar problem:
Flux<Integer> body = Flux.range(1, 50).log(); // Simulate flow of data
ConnectableFlux<Integer> sharedBody = body.publish(1); // Content is already buffered - ideal prefetch would be 0
AtomicLong readCount = new AtomicLong(); // Counter
AtomicReference<Flux<Integer>> partiallyCachedFlux = new AtomicReference<>(); // A hack, not to be used in real world
Flux.from(sharedBody)
.takeUntil(s -> {
System.out.println("C:" + s);
return readCount.incrementAndGet() >= 10; // Store up to 10 elements
})
.collectList()
.subscribe(ints -> {
System.out.println("Collected:" + ints); // Log what we got
partiallyCachedFlux.set(
Flux.concat(Flux.fromIterable(ints).log(), sharedBody)
); // Create a flux that contains captured data and remaining data
});
sharedBody.connect();
Thread.sleep(1000); // Because I was lazy
partiallyCachedFlux.get()
.doOnEach(i -> { if (i.isOnNext()) System.out.println("P:" + i.get());})
.subscribe(); // Show that we have captured everything
The opposite of takeUntil is skipUntil. You could share the original flux into 2 flux, one of which takesUntil and the other skipsUntil. Your end result would simply be the Flux.merge of both flux.
Note that when externalizing state like this (AtomicInteger), you'll run into problems if the whole Flux is subscribed to multiple times. The way to work around that is to wrap everything into a Flux.defer, so that the external state is created within the lambda and thus specific to a given subscription.

RxJava - two observables

I've got two observables - OnPeriodChanged and OnFilterChanged, and trying to figure out how to call a function for view adapter when one of them changes. I've tried .zip, but for some reason it does not get triggered:
Observable.zip(OnPeriodChanged, OnFilterChanged, (Date, Filter) -> HistoryViewModel.getScans(Date.first, Date.second, Filter)).subscribe(scans -> histAdapter.setScans(scans));
What I can use here to invoke the getter function and pass the results from it to setter?
zip will emit the items to the downstream only after both of your observables (OnPeriodChanged, OnFilterChanged) emitted. I think you are trying to
call HistoryViewModel.getScans whenever any of the item changes, with latest values of Date and Filter. You could use combineLatest instead of zip
Try changing it to
Observable.combineLatest(OnPeriodChanged, OnFilterChanged, (Date, Filter) -> HistoryViewModel.getScans(Date.first, Date.second, Filter))
.subscribe(scans -> histAdapter.setScans(scans));

RXAndroid: get List objects from the difficult List

I have been trying for a long time to execute this code on Android, looking for answers here but not successfully. I'm a beginner developer, and please understand me.
I make multiple requests using Retrofit2 and RXJava
There is the answer, JSON (Array), it's class CurrencyData in Java
[
{
"r030":978,"txt":"euro","rate":11.11111,"cc":"EUR","exchangedate":"25.09.2018"
}
]
MyAPI interface
#GET("/BankService/v1/statdirectory/exchange")
Observable<List<CurrencyData>> getCurrencyCodeDate(#Query("valcode") String valCode, #Query("date") String date);
collect requests
List<Observable<List<CurrencyData>>> requests = new ArrayList<>();
requests.add(myApi.getCurrencyCodeDate("USD","20180928"));
requests.add(myApi.getCurrencyCodeDate("EUR","20180928"));
execute requests
Observable
.zip(requests, Arrays::asList)
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
I got
[[CurrencyData{txt='Dollar USA', rate='1.111'}], [CurrencyData{txt='EURO', rate='2.222'}]]
I'm interested in how to get a List<CurrencyData> with all the CurrencyData objects.
I tried to use map and flatMap - but it does not work.
I will be very grateful.
Thank you.
Problem:
Your getCurrencyCodeDate method returns Observable of List<CurrencyData>, And you are using it inside a Observable.zip() whith zipper func as Arrays::asList.
What is happening is, Your getCurrencyCodeDate emits a List of objects and Arrays::asList is wrapping all the emitted Lists in a List. Resulting in Observable.zip() emitting List<List<>>.
On top of that .toList() operator is applied, which will again wrap emitted List<List<>> inside another List resulting in List<List<List<>>> as return type.
What you could do is, use merge operator instead of zip and use collectInto instead of toList.
Observable
.merge(requests)
.collectInto(new ArrayList<CurrencyData>(), ArrayList::addAll)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

How to use switchIfEmpty RxJava

The logic here is that if the ratings in the database are empty, then I want to get them from the API. I have the following code:
Observable.from(settingsRatingRepository.getRatingsFromDB())
.toList()
.switchIfEmpty(settingsRatingRepository.getSettingsRatingModulesFromAPI())
.compose(schedulerProvider.getSchedulers())
.subscribe(ratingsList -> {
view.loadRatingLevels(ratingsList, hideLocks);
}, this::handleError);
The getRatingsFromDB() call returns List<SettingRating>, but the API call returns Observable<List<SettingRating>>.
However, when I unit test this, when I pass an empty list from the database call, it does not execute the API call. Can someone pls help me in this matter. This is my unit test code:
when(mockSettingsRatingsRepository.getRatingsFromDB()).thenReturn(Collections.emptyList());
List<SettingsRating> settingsRatings = MockContentHelper.letRepositoryReturnSettingsRatingsFromApi(mockSettingsRatingsRepository);
settingsParentalPresenter.onViewLoad(false);
verify(mockView).loadRatingLevels(settingsRatings, false);
As #Kiskae mentioned, it's the fact that I am confusing an empty list with an empty Observable. Therefore, I have used the following which is what I want:
public void onViewLoad(boolean hideLocks) {
Observable.just(settingsRatingRepository.getRatingsFromDB())
.flatMap(settingsRatings -> {
if (settingsRatings.isEmpty()) {
return settingsRatingRepository.getSettingsRatingModules();
} else {
return Observable.just(settingsRatings);
}
})
.compose(schedulerProvider.getSchedulers())
.subscribe(ratingsList -> {
view.loadRatingLevels(ratingsList, hideLocks);
}, this::handleError);
}
Observable#toList() returns a single element. If the observable from which it gets its elements is empty, it will emit an empty list. So by definition the observable will never be empty after calling toList().
switchIfEmpty will only be called when your observer completes without emitting any items.
Since you are doing toList it will emit list object. Thats why your switchIfEmpty is never getting called.
If you want to get data from cache and fallback to your api if cache is empty, use concat along with first or takeFirst operator.
For example:
Observable.concat(getDataFromCache(), getDataFromApi())
.first(dataList -> !dataList.isEmpty());
Building on answer by #kiskae, your use of a toList() emits the elements aggregated as a single List.
There is an alternative to the use of Observable.just() + a flatMap here.
Observable.from will iterate over the list returned by your service and emit each individual items, so an Observable<Rating>. If said list is empty, it naturally produces an empty Observable. Your API call also produces an Observable<Rating>, and in both cases you want to reaggregate that back into a List<Rating> for further processing.
So just move the toList() from your original code down one line, after the switchIfEmpty calling the API:
Observable.from(settingsRatingRepository.getRatingsFromDB())
.switchIfEmpty(settingsRatingRepository.getSettingsRatingModulesFromAPI())
.toList()
.compose(schedulerProvider.getSchedulers())
.subscribe(ratingsList -> {
view.loadRatingLevels(ratingsList, hideLocks);
}, this::handleError);
Granted, that solution may produce a bit more garbage (as the db's List is turned into an Observable just to be turned into a new List later on).

Categories

Resources