I'm new to android, so have some questions regarding api calls.
Currently i use Retrofit to accomplish my api calls.
Here is example of my api call with retrofit
#POST("posts/new")
fun createPost(#Body post: Post, #Header("Authorization") token: String): Single<PostResult>
So, assume i have 10 posts and i need to call createPost 10 times (Yes, i know i can have list input on BE side, but ... ). Best way is to iterate over posts (for/map) and send them to the server.
But here is problem:
- How do i can track that all calls are done?
In JS i can have something like Promise.all - could i do something
similar in android?
I thought about counting the finished vs started requests, but i think it's bit ugly isn't?
in your response success you need to call again get/post method.
after success you know your api call is done or else it throw error.
Thanks to #shkschneider
Your question is broad. You could use RxJava to zip, or coroutines to async/await or other methods.
Ended up with usage of .zip
Single.zip(observables) { args -> args }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ ...Success }, { ...Failure })
Related
So I have a java app that calls 2 APIs.
Call an API to get a request a file to be generated
Call the second API to get the file.
The first API returns the credentials to get the file. The Second one returns the file but it may take a few seconds or minutes to be generated. What is the best way to account for the time delay between asking the file to be generated and the file being available to pull? Some retry logic should be necessary but on the initial call it always returns a 4xx HTTP response. What's the best way to make this api call maybe there's a better way than using RestTemplate to sequentially call the 2 apis? I thought of adding a short time delay before the 2nd call but I was wondering if there is a better library or async method I can use that's more efficient.
I'd appreciate any 2 cents thanks!
If the author of these two apis is a partner of yours, I think there's a better way, like, the file generator call a callback api of yours and then you call the second API to get the file. And as a supplement, considering unexpected exceptions during above process, a retry schedule to fetch the missed file is probably necessary.
But if you just want to implement the retry and async code more gracefully, the following is my idea
//call the first API
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
CompletableFuture<File> completionFuture = new CompletableFuture<>();
final ScheduledFuture<?> checkFuture = executor.scheduleAtFixedRate(() -> {
//Call the second API to get the file
//if("have got the file successfully") {
// completionFuture.complete(the file);
//} else {
// //do nothing
//}
}, 5, 1, TimeUnit.SECONDS);//set a reasonable schedule policy
completionFuture.whenComplete((file, thrown) -> {
//do something when the retry schedule complete
checkFuture.cancel(false);
});
completionFuture.orTimeout(10, TimeUnit.SECONDS);//setting a timeout policy is probably necessary
I want to iterate through all pages of a given url and collect JSON objects. With this code I'm getting list of 10 objects:
List<EzamowieniaDto> ezam = WebClient
.create("https://ezamowienia.gov.pl/mo-board/api/v1/Board/Search?noticeType=ContractNotice&isTenderAmountBelowEU=true" +
"&publicationDateFrom=2022-03-16T00:00:00.000Z&orderType=Delivery&SortingColumnName=PublicationDate&SortingDirection=DESC" +
"&PageNumber=1")
.get()
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<EzamowieniaDto>>(){})
.block();
I've tried to just delete "PageNumber" from request, but it seems the pagination is hard-coded for this page.
(X-Pagination header from response: [{"TotalCount":88,"PageSize":10,"CurrentPage":1,"TotalPages":9,"HasNext":true,"HasPrevious":false}])
The question is: How can I iterate through number of pages mentioned in response header, and collect the whole data?
Here is the way you could handle paginaged requests with WebClient.
Create a method to retreive a single page of data. Typically you would use bodyToFlux(EzamowieniaDto.class) and return Flux<EzamowieniaDto> but because we need headers we have to use toEntityFlux(EzamowieniaDto.class) to wrap response in Mono<ResponseEntity.
Mono<ResponseEntity<Flux<EzamowieniaDto>>> getPage(String url, int pageNumber) {
return webClient.get()
.uri(url + "&PageNumber={pageNum}", pageNumber)
.retrieve()
.toEntityFlux(EzamowieniaDto.class);
}
Use expand to to fetch data until we reach the end
Flux<EzamowieniaDto> getData(String url) {
return getPage(url, 1)
.expand(response -> {
Pagination pagination = formJson(response.getHeaders().getFirst("X-Pagination"));
if (!pagination.hasNext()) {
// stop
return Mono.empty();
}
// fetch next page
getPage(url, pagination.getCurrentPage() + 1);
})
.flatMap(response -> response.getBody());
}
Firstly, do not use .block() method, because, shortly, it interrupts asynchronous stream and makes it synchronous, so there is no need, actually, in WebClient nowadays (here you can find some brief intro), to use it in such way. You can use, also, RestTemplate implementations like Retrofit. But in your case, to save the asynchronous pattern, you can use next solution:
List<EzamowieniaDto> ezam = WebClient
.create("https://ezamowienia.gov.pl/mo-board/api/v1/Board/Search?noticeType=ContractNotice&isTenderAmountBelowEU=true" +
"&publicationDateFrom=2022-03-16T00:00:00.000Z&orderType=Delivery&SortingColumnName=PublicationDate&SortingDirection=DESC" +
"&PageNumber=1")
.get()
.retrieve()
.bodyToFlux(EzamowieniaDto.class) // here you can just use Flux, it's like List from synchronous Java, simply
.map({body -> /*body = EzamowieniaDto , do any job with this object here*/})
...
Example
...
List<EzamowieniaDto> dtos = new ArrayList<>();
Flux<EzamowieniaDto> fluxDtos = WebClient
.create("http://some-url.com")
.get()
.retrieve()
.bodyToFlux(EzamowieniaDto.class)
.filter({body -> body.getId().equals(1L)}) // here just some filter for emitted elements
.subscribe({dto -> dtos.add(dto)}); // subscribe to end asynchronous flow , in simple words
System.out.println(dtos.get(0).getId().equals(1L); // some simple test or something like this, use dtos as you need.
Additionally
Using synchronous staff (Lists, Mono of List, etc.) mixed with asynchronous, you will always get synchronous behavior at some point of time, in the place in your code where it happens. Reactive programming implies that you use asynchronous programming (mostly, declarative programming) while the whole process from fetching asynchronously response to asynchronously writing to the database (Hibernate Reactive, for example).
Hope it helps somehow and I suggest to start learning reactive programming (Reactor or Spring WebFlux, for example), if you are not started yet to understand basics of asynchronous programming.
Best Regards, Anton.
I want to make an api request, then I need to make two more requests after I receive the data. I found a great SO answer that uses rxjava2 to make two concurrent requests here:
How to make multiple request and wait until data is come from all the requests in retrofit 2.0 - android
I suppose I could just chain the logic for this after the first request, but my intuition tells me thats a bad idea because I'd be duplicating some code (I'd have separate logic for the first request in a function, then some similar logic for the second two requests in a function)
Is there a better way to accomplish this? I'd prefer Kotlin, but Java is ok.
Here is the code for concurrent requests from the SO answer.
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.build()
val backendApi = retrofit.create(MyBackendAPI::class.java)
val requests = ArrayList<Observable<*>>()
requests.add(backendApi.getUser())
requests.add(backendApi.listPhotos())
requests.add(backendApi.listFriends())
Observable
.zip(requests) {
// do something with those results and emit new event
Any() // <-- Here we emit just new empty Object(), but you can emit anything
}
// Will be triggered if all requests will end successfully (4xx and 5xx also are successful requests too)
.subscribe({
//Do something on successful completion of all requests
}) {
//Do something on error completion of requests
}
Thanks
Small question regarding Spring Webflux Webclient, and how to increase the client side time out please.
Setup, I am the client, I need to consume a third party API over the web.
This third party API is known to be flaky. For a same request, same response, sometimes, it takes 1-2sec, sometimes more than 4-5secs (mostly 4-5secs 😛)
But it is a good and important API, the payload response is very important.
Hence, I believe it is worth to "wait them longer".
May I ask how to do that please?
After investigation, this third party API do see some 499 response from their VIP.
I believe this means "I did not want to wait". But I do want to wait longer!
After looking at the Webclient API, I am having a hard time finding how to do this.
Currently, I am constructing my WebClient as such:
WebClient.create().mutate().defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE, HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE).clientConnector(new ReactorClientHttpConnector(HttpClient.create().metrics(true, () -> new MicrometerChannelMetricsRecorder(SERVICE, HTTP)).wiretap(true).secure(sslContextSpec -> sslContextSpec.sslContext(getSslContextBuilder())))).build()
What is the current default time out?
How to increase it please via property or code?
Something like :
.setDefaultClientSideWaitTime() ?
Many thanks for your help
Per answer from Netty Team, it needs:
.responseTimeout(Duration.ofSeconds(10))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10 * 1000)
For an example to wait 10 seconds
and
conn.addHandlerLast
.timeout(Duration.ofSeconds(10L))
Are not needed
Try to set a responseTimeout as done for WebTestClient here :
https://stackoverflow.com/a/48655749/4725964
I am using Retrofit2 /RxJava on Android. My POST call works fine in general. But now I added the call from another location. When invoked, there is no HTTP post request sent, no errors/exceptions. HttpInterceptor isn't seeing the call either. Its hard for me to find out what I am doing wrong in this instance.
mAccountManager.onAuthChange()
.subscribeOn(Schedulers.io())
.subscribe(authCode -> {
if (mAccountManager.isLoggedIn(authCode)) {
someOtherApi.getIds()
.subscribeOn(Schedulers.io())
.subscribe(idList -> {
mUserApi.doSomething(idList);
});
}
});
...
#POST("/api/users/dosomething")
Observable<Void> doSomething(#Body IdList idList);
I made sure retrofit api is called by putting breakpoint.
Any idea what I am missing?
Retrofit's rx-adapter creates cold observable, i.e it won't do anything until you subscribe to it. so the problem for you is, you are not subscribing to mUserApi.doSomething(idList); API call.
so just subscribe to it & it'll get called. Also I am not sure about the code of mAccountManager.onAuthChange(), but you should have the onError part as well inside the subscribe to avoid UndeliverableException