I am trying to send an io.reactivex.Flowable from a Spring RestController to an Android application that uses Retrofit and Rxjava. If I use the browser to check what the Rest endpoint returns, I get a series of values as expected but in Android I get only one value and then it calls the onComplete method. What am I missing?
Spring Controller:
#GetMapping("/api/reactive")
public Flowable<String> reactive() {
return Flowable.interval(1, TimeUnit.SECONDS).map(sequence -> "\"Flowable-" + LocalTime.now().toString() + "\"");
}
Retrofit repository:
#GET("reactive")
Flowable<String> testReactive();
Main service:
public useReactive() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Values.BASE_URL)
.addConverterFactory(JacksonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
userRepository = retrofit.create(UserRepository.class);
Flowable<String> reactive = userRepository.testReactive();
Disposable disp = reactive.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new ResourceSubscriber<String>() {
#Override
public void onNext(String s) {
logger.log(Level.INFO, s);
Toast.makeText(authActivity, s, Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable t) {
t.printStackTrace();
}
#Override
public void onComplete() {
logger.log(Level.INFO, "Completed");
Toast.makeText(authActivity, "Completed", Toast.LENGTH_SHORT).show();
}
});
}
Upon calling the useReactive() method, I get only one value "Flowable-..." and then "Completed".
Even though the Retrofit service has return type Flowable<String>, calling testReactive() will only make one HTTP call on the Android device.
The type Flowable is merely for compatibility, in practice it will end up being a Flowable that emits a single value and then terminates.
This is just how Retrofit works.
You would need to find another solution if you want to continually receive new values that are being emitted from the server, perhaps GRPC or polling the server.
Related
I am developing a simple github client that retrieves a list of repositories from a particular username.
I have this method in my activity:
private void subscribeRepos(Observable<List<Repository>> repository) {
disposable.add(repository
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<List<Repository>>() {
#Override
public void onComplete() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(List<Repository> list) {
adapter.setItems(list);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> onNext Called");
}
}));
}
This is my Retrofit service:
public class RetrofitService {
private static final String BASE_URL = "https://api.github.com/";
private RepoAPI repoAPI;
private static RetrofitService INSTANCE;
/**
* Method that returns the instance
* #return
*/
public static RetrofitService getInstance() {
if (INSTANCE == null) {
INSTANCE = new RetrofitService();
}
return INSTANCE;
}
private RetrofitService() {
Retrofit mRetrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(BASE_URL)
.build();
repoAPI = mRetrofit.create(RepoAPI.class);
}
/**
* Method that returns the API
* #return
*/
public RepoAPI getRepoApi() {
return repoAPI;
}
}
And my RepoAPI interface
public interface RepoAPI {
#GET("/users/{user_name}/repos")
Observable<List<Repository>> getRepositories(#Path("user_name") String userName);
}
So, whenever I actively call subscribeRepos(mainViewModel.getRepositories("whateverusername")); onNext is triggered as expected. But if I manually create a new repository on my github account, onNext is not called. Shouldn't onNext be called anytime I add or remove a new repo on my github account?
This is not actually how reactive streams work with network requests using Retrofit.
With a network request, once you have subscribed for an event and you receive its data, that's it. The stream is completed (you can check this logging onComplete callback).
Although you can make operations like map, switch, concat, and others with it, it is not a "real time" subscription.
As said here: "Retrofit Network call with RxJava: Use Single : As our API will not give data in a pieces or multiple times. Instead it will emit everything in just one call. So, in case of Observable onCompleted() will follow as soon as onNext() happens."
If you want something (almost) real time you could schedule a job to make this api call every few minutes (or seconds, or any time period that you want). Be aware with data leaks and thread handling!
I am new to android and I have a scenario where I want to get get data from multiple api. Let suppose api_a, api_b, api_c, api_d. These api are independent of each other but I want to show data from these api in a mix Recycler View (horizontal and vertical). So I want to make these api call in such a manner so that I can get every api data at a time so that i can display in recycler view.
I already using retrofit 2 but for that I had to chain them one by one which is very lengthy and I think this is not a feasible approach. I know little bit about RX JAVA ,but I only know how to make one request at a time. Please help
There are at least 2 ways to achieve this -
1) Using RxJava Zip operator (for parallel requests)
Get all the observables
Observable<ResponseType1> observable1 = retrofit.getApi_a();
Observable<ResponseType2> observable2 = retrofit.getApi_b();
Observable<ResponseType3> observable3 = retrofit.getApi_c();
Zip the observables to get a final observable
Observable<List<String>> result =
Observable.zip(observable1.subscribeOn(Schedulers.io()), observable2.subscribeOn(Schedulers
.io()), observable3.subscribeOn(Schedulers.io()), new Function3<ResponseType1, ResponseType2, ResponseType3, List<String>>() {
#Override
public List<String> apply(ResponseType1 type1, ResponseType2 type2, ResponseType3 type3) {
List<String> list = new ArrayList();
list.add(type1.data);
list.add(type2.data);
list.add(type3.data);
return list;
}
});
now subscribe on the resultant observable
result.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new Observer<List<String>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<String> s) {
Log.d(TAG, "s is the list with all the data");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, e.getMessage());
}
#Override
public void onComplete() {
}
});
2) Using RxJava flatMap() operator. (To request serially one after another)
This is simple chaining of requests
List<String> result = new ArrayList<>();
Disposable disposable = retrofit.getApi_a()
.subscribeOn(Schedulers.io())
.flatMap((Function<ResponseType1, ObservableSource<ResponseType2>>) response1 -> {
result.add(response1.data);
return retrofit.getApi_b();
})
.flatMap((Function<ResponseType2, ObservableSource<ResponseType3>>) response2 -> {
result.add(response2.data);
return retrofit.getApi_c();
})
.map(response3 -> {
result.add(response3.data);
return response3;
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<Response3>() {
#Override
public void onNext(Response3 response3) {
Log.d(TAG, "result variable will have all the data");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, e.getMessage());
}
#Override
public void onComplete() {
}
});
For combining multiple Observables you may want to consider the Merge operator.
This would allow you to combine the stream of multiple requests into a single Observable.
Merge will interleave them as they are emitted. If sequence matters, there is also Concat which will emit from each Observable before continuing with the next.
Rx Doc
Merge: http://reactivex.io/documentation/operators/merge.html
Concat: http://reactivex.io/documentation/operators/concat.html
Merge operator combines multiple observable into one
Set up Base URL of API:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(oktHttpClient.build())
.build();
Now setup two observables for the two network requests:
Observable<JsonElement> Observable1 = ApiClient.getApiService().getApi_1();
Observable<JsonElement> Observable2 = ApiClient.getApiService().getApi_2();
Now we use RxJava's mergemethod to combine our two Observables:
Observable.merge(Observable1, Observable2 )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<JsonElement>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(JsonElement value) {
Log.d("RESPONSE", "onNext:=======" + value);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
Log.d("RESPONSE", "DONE==========");
}
});
I'm currently trying to use RxJava with Retrofit for the first time but can't seem to get anything working for my specific use case:
I begin by calling an API using retrofit to show cinemas near a users location.
I then use the cinema id which the user clicks on to display showtimes for this cinema i.e...
public interface ListingApiService
{
#GET("/get/times/cinema/{id}")
Call<ListingResponse> getShowtimes (#Path("id") String id);
}
Then using the interface....
public void connectAndGetApiData(String id)
{
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
ListingApiService listingApiService = retrofit.create(ListingApiService.class);
Call<ListingResponse> call = listingApiService.getShowtimes(id);
call.enqueue(new Callback<ListingResponse>() {
#Override
public void onResponse(Call<ListingResponse> call, Response<ListingResponse> response)
{
List<Listing> listings = response.body().getListings()
getAndDisplayImage(listings.get(0).getTitle());
recyclerView.setAdapter(new ListingAdapter(listings,R.layout.list_item_listing,getApplicationContext()));
}
#Override
public void onFailure(Call<ListingResponse> call, Throwable t)
{
Log.e(TAG,t.toString());
}
});
}
I then want to call a different API (contextual web search) to display an image of a relevant movie poster (just for a nice visual effect) for each movie listing. I know how to call the API for a single image, but I don't know how to make multiple calls. I've tried using RxJava code found elsewhere on the internet but none of it seems to work as I don't have prior knowledge of how many calls I will be making or what the search term will be. The code i'm using for a single call is:
public interface ListingImageApiService
{
//https://contextualwebsearch-websearch-v1.p.mashape.com/api/Search/ImageSearchAPI?count=1&autoCorrect=false&q=Donald+Trump
#Headers("X-Mashape-Key: apikey")
#GET("/api/Search/ImageSearchAPI?count=5&autoCorrect=false")
Call<ListingImageResponse> getListingImages (#Query("q") String term);
}
public void getAndDisplayImage(String search)
{
if (retrofit2 == null)
{
retrofit2 = new Retrofit.Builder()
.baseUrl(BASE_URL2)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
search = search + " poster";
ListingImageApiService listingImageApiService = retrofit2.create(ListingImageApiService.class);
Call<ListingImageResponse> call = listingImageApiService.getListingImages(search);
call.enqueue(new Callback<ListingImageResponse>() {
#Override
public void onResponse(Call<ListingImageResponse> call, Response<ListingImageResponse> response)
{
System.out.println(response.body().toString());
ListingImage a = new ListingImage();
List<ListingImage> listingImages = response.body().getListingImage();
System.out.println(listingImages.get(0).getUrl());
}
#Override
public void onFailure(Call<ListingImageResponse> call, Throwable t)
{
}
});
}
My question is, how would I use RxJava to make multiple calls using data for the list of movie titles of unknown size (which I can pass to getAndDisplayImage instead of a single string)? I have made several attempts but none seem to work for my use case. Thank you.
This design should solve your problem.
This interface contains the endpoints used in the application.
public interface ListingApiService
{
#GET("/get/times/cinema/{id}")
Observable<List<MovieResponse>> getShowtimes (#Path("id") String id);
#Headers("X-Mashape-Key: apikey")
#GET("/api/Search/ImageSearchAPI?count=5&autoCorrect=false")
Observable<ListingImageResponse> getListingImages (#Query("q") String term);
}
Method which provides the retrofit object to make the call
private API getAPI() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("<your API endpoint address")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return retrofit.create(API.class);
}
Make the call to get the List<MovieResponse>. This method also converts the List into a individual observable MovieResponse object.
private void getMovieListingsWithImages() {
Observer<MovieResponse> observer = new Observer<MovieResponse>() {
#Override
public void onSubscribe(Disposable d) {
Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT).show();
}
#Override
public void onNext(MovieResponse movieResponse) {
//for each movie response make a call to the API which provides the image for the movie
}
#Override
public void onError(Throwable e) {
Toast.makeText(getApplicationContext(), "Error getting image for the movie", Toast.LENGTH_SHORT).show();
}
#Override
public void onComplete() {
Toast.makeText(getApplicationContext(), "Finished getting images for all the movies in the stream", Toast.LENGTH_SHORT).show();
}
};
getAPI().getShowtimes()
.flatMapIterable(movieResponseList -> movieResponseList) // converts your list of movieResponse into and observable which emits one movieResponse object at a time.
.flatMap(this::getObservableFromString) // method converts the each movie response object into an observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
method which converts the MovieResponse object into an Observable.
private Observable<MovieResponse> getObservableFromString(MovieResponse movieResponse) {
return Observable.just(movieResponse);
}
I have an application in Android, it sends several data in short time. Aprox. 2500 request.
This process is very time-consuming.
What advice can you give me to improve the time?
Thanks
You can use multiple Thread to send data to the server in the background.
If you are updating UI component after the execution use AsyncTask. But you have to run AsyncTask parallelly. You can do that by AsyncTaskCompat.executeParallel(Your AsyncTask);
If you wish to send data even your app closed. You can use service.
I'd recommend using Retrofit. It handles a lot of threading issues you might be struggling with.
Here's an example from their website.
You'd create an interface for the API you're looking to receive:
public interface GitHubService {
#GET("users/{user}/repos")
Call<List<Repo>> listRepos(#Path("user") String user);
}
You build a retrofit class
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
And finally you get a call object:
Call<List<Repo>> repos = service.listRepos("octocat");
Consuming the call object requires enqueueing a Callback. Here's an example using a different Retrofit service (TaskService in this case):
TaskService taskService = ServiceGenerator.createService(TaskService.class);
Call<List<Task>> call = taskService.getTasks();
call.enqueue(new Callback<List<Task>>() {
#Override
public void onResponse(Call<List<Task>> call, Response<List<Task>> response) {
if (response.isSuccessful()) {
// tasks available
} else {
// error response, no access to resource?
}
}
#Override
public void onFailure(Call<List<Task>> call, Throwable t) {
// something went completely south (like no internet connection)
Log.d("Error", t.getMessage());
}
}
Source
Im making an asynchronous call and im able to send a request to the server and getting my desired JSON(using postman) but for some reason the OnResponse and OnFailure methods are not being executed, may you please help me if im missing out something.Im using retrofit2
private List<Advert> getAdverts(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl( "http://192.168.100.4:8092/portal/")
.client(client.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
RequestService requestService = retrofit.create(RequestService.class);
Call<List<Advert>> customerCall= requestService.getAdverts(accountNumber);
System.out.println("++++++++++++++ request was made +++++++++++++++++++");
customerCall.enqueue(new Callback<List<Advert>>() {
#Override
public void onResponse(Call<List<Advert>> call, Response<List<Advert>> response) {
Log.d("onResponse", "" + response.message());
System.out.println("++++++++++++++ was here+++++++++++++++++++");
if (response.isSuccessful()) {
adverts.addAll(response.body());
} else {
Toast.makeText(getContext(), "" + response.message(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<List<Advert>> call, Throwable t) {
Log.d("onFailure", t.toString());
}
});
return adverts;
}
Not sure, but you're returning before de operation has ended (as you mentioned is a asynchronous call). So as soon you enqueue, you're returning "adverts" (probably cancelling your call).
Consider using other alternatives to return your response, like Callbacks, EventBus, persist the response on local storage, etc.