RxJava Observable and retrofit API calls - android

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!

Related

Android Retrofit + Rxjava flowable completes too early

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.

RxJava / Retrofit API Call for every item in a list of unknown size

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);
}

Retrofit subsequent calls not working

I have something like this
Retrofit retrofit =new retrofit2.Retrofit.Builder()
.baseUrl("URL")
.addConverterFactory(GsonConverterFactory.create())
.build();
requestService = retrofit.create(RequestInterface.class);
call = requestService.getData(page);
call.enqueue(new Callback<List<Cats>>() {
#Override
public void onResponse(Call<List<Cats>> call, Response<List<Cats>> response) {
....
}
#Override
public void onFailure(Call<List<Cats>> call, Throwable t) {
...
}
});
However when i want to get the second page, when i make a request for the second page within the same class, retrofit callback methods is not getting called.
call = requestService.getData(page); // page incremnted
call and requestService is globally defined
in Retrofit, each "call" instance is linked to one API call (single network request) and cannot be reused. You can reuse your RetrofitSerive instance, but for every new API call you will have to create a new Call object and enqueue it separately
You can use a generic response and use an url each time.
#GET
Call<Generic<T>> getUsers(#Url String url);

Multiple retrofit calls are being send but some never receive response or error

General description
When I'm sending several requests in a short time, some of them are never getting response or error.
API
I've got an singleton class that keeps retrofit service in it. I use this class to perform all calls to api, every call returns Observable with some kind data.
public class CoreApi {
public static final String BASE_URL = "https://www.example.com/";
private static CoreApi instance;
private CoreApiService service;
public static CoreApi get() {
if (instance == null)
instance = new CoreApi();
retrun instance;
}
private CoreApi() {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
if (BuildConfig.DEBUG)
httpClient.addInterceptor(httpLoggingInterceptor);
httpClient.connectTimeout(60, TimeUnit.SECONDS);
httpClient.readTimeout(60, TimeUnit.SECONDS);
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create())
.client(httpClient.build());
service = (builder.build()).create(CoreApiService.class);
}
public Observable<SomeData> getSomedata(String authorization) {
return service.getSomeData(authorization);
}
}
SERVICE
interface CoreApiService {
#GET("someDataEndpoint/")
Observable<SomeData> getSomeData(#Header("Authorization") String authorizationToken);
}
CALL
I've setup a button in activity that everytime it is clicked, it performs call to api:
Button button = (Button) findViewById(R.id.test_button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
CoreApi.get().getSomeData("JWT thisisexampletokenreplacedforthesakeofthisquestion")
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Action1<SomeData>() {
#Override
public void call(SomeData someData) {
// operations are performed on data, irrelevant for the issue as even if I comment them the issue still occurs
}
});
}
});
ISSUE
Whenever I click on the button(not too fast), in logs I can see that the request is being made by retrofit.
But when I start to click the button a little bit faster, I can see in logs that requests are being send, but not all of them receive the response. Theres no error, theres no timeout, theres nothing. In logcat I can only see that the request have been made(see below).
09-19 11:26:05.421 18763-18821/com.myapp.app D/OkHttp: --> GET https://www.example.com/someDataEndpoint/ http/1.1
09-19 11:26:05.421 18763-18821/com.myapp.app D/OkHttp: Authorization: JWT thisisexampletokenreplacedforthesakeofthisquestion
09-19 11:26:05.422 18763-18821/com.myapp.app D/OkHttp: --> END GET
SUMMARY
The example above is simplified, but this issue occurs only when there are a lot of calls in a short time(not necessarily to the same endpoint).
At first I noticed it when my HandlerThread responsible of refreshing user data from several endpoint in a specified sequence started to getting stuck in random points, sometimes at 2nd, sometimes at 10th call and sometimes somewhere in between. Weird thing is that after one of those calls gets stuck, I can still perform other calls from other places in application.
CoreApi.get().getSomeData(//Your value here instead of the key)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Action1<SomeData>() {
#Override
public void call(SomeData someData) {
// operations are performed on data, irrelevant for the issue as even if I comment them the issue still occurs
}
});
Your are passing string instead of actual value in the authorization header.Change your authorization token code to value instead of key.

can't get data outside onResponse Retrofit

I can't get the data outside onResponse in Restrofit
this is my code
List<Categorie> categorylist=new ArrayList<>();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
final CategoryApi api = retrofit.create(CategoryApi.class);
Call<List<Categorie>> categoryCall = api.categories();
categoryCall.enqueue(new Callback<List<Categorie>>() {
#Override
public void onResponse(Call<List<Categorie>> call, Response<List<Categorie>> response) {
categorylist = (List<Categorie>)response.body();
Log.i("success","Inside "+categorylist.toString());
// here i get the data
}
#Override
public void onFailure(Call<List<Categorie>> call, Throwable t) {
System.out.println("Erreur");
}
});
Log.i("success","Outside "+categorylist.toString());
// here i get null
i've tried making categorylist volatile and static and it didn't work
It appears you're trying to access categorylist immediately after calling categoryCall.enqueue. enqueue is an asynchronous operation which will not complete before the next line ( containing categorylist.toString()) is executed.
Try calling another method where you've left the comment "here i get the data"
public void onResponse(/*...*/ response) {
categorylist = (List<Categorie>)response.body();
Log.i("success","Inside "+categorylist.toString());
// here i get the data
businessLogic(categorieslist);
}
private void businessLogc(List<Categorie> categories) {
myView.showCategories(categories); // or whatever you're doing with data
}
You know, http request is a process that require time, so it should be done asynchronously. Fortunately retrofit handle everything for us.
What you need to do, is when we get response from OnResponse, you need to update your current view, for example if you're using recyclerview, there is recyclerview.notifyDataSetChanged() method.
If you want to get the result from beginning. I'm suggest you call the enqueue method in other activity or somewhere else, and then when you get the result, save it into sqlite database.
And then when you open CategoryActivity load it from database.
You can add refresh button to call the enqueue again to update the database.

Categories

Resources