In my android app, I want to make multiple http requests using retrofit and rxjava to fetch json data. The number of request depends on the user's preferences (1 up to 40). Each request is independent and returns same type. So, i tried to apply the way that is recommended in this question (How to make multiple request and wait until data is come from all the requests in retrofit 2.0 - android) which uses the zip function of rx-java. But i couldn't find a way to get and combine the results of each request. Response type that i used for single request in retrofit was Response<List<NewsItem>> where NewsItem is my custom object. (the response is json array actually but in a single request retrofit automatically handles it and converts it into list of my custom object) What i tried so far is below:
My API Interface
public interface API {
String BASE_URL = "xxx/";
#GET("news/{source}")
Observable<List<NewsItem>> getNews(#Path("source") String source);
}
Viewmodel class to fetch data
public class NewsVM extends AndroidViewModel {
public NewsVM(Application application){
super(application);
}
private MutableLiveData<List<NewsItem>> newsLiveData;
public LiveData<List<NewsItem>> getNewsLiveData(ArrayList<String> mySourceList) {
newsLiveData = new MutableLiveData<>();
loadNews(mySourceList);
return newsLiveData;
}
private void loadNews(ArrayList<String> mySourceList) {
Gson gson = new GsonBuilder().setLenient().create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
API api = retrofit.create(API.class);
//Gathering the requests into list of observables
List<Observable<?>> requests = new ArrayList<>();
for(String source: mySourceList){
requests.add(api.getNews(source));
}
// Zip all requests
Observable.zip(requests, new Function<Object[], List<NewsItem>>() {
#Override
public List<NewsItem> apply(Object[] objects) throws Exception {
// I am not sure about the parameters and return type in here, probably wrong
return new ArrayList<>();
}
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.subscribe(
new Consumer<List<NewsItem>>() {
#Override
public void accept(List<NewsItem> newsList) throws Exception {
Log.d("ONRESPONSE",newsList.toString());
newsLiveData.setValue(newsList);
}
},
new Consumer<Throwable>() {
#Override
public void accept(Throwable e) throws Exception {
Log.d("ONFAILURE", e.getMessage());
}
}
).dispose();
}
}
It doesn't give error but doesn't give response also since I couldn't handle the response. Can anybody help me in combining the results of the each request? I've searched all the questions but can't find an example like this.
try to use Observable.from(Iterable<? extends T> iterable) (Observable.fromArray() in rx-java2) instead of zip
So you'll have something like:
Observable.from(mySourceList)
.flatMap(new Func1<String, Observable<List<NewsItem>>>() {
#Override
public Observable<List<NewsItem>> call(String source) {
return api.getNews(source);
}
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.toList() // This will give you List<List<NewsItem>>
.map(new Func1<List<List<NewsItem>>, List<NewsItem>>() {
#Override
public List<NewsItem> call(List<List<NewsItem>> listOfList) {
//Merged list of lists to single list using Guava Library
List<NewsItem> list = Lists.newArrayList(Iterables.concat(listOfList));
return list;
}
})
.subscribe(new Subscriber<List<NewsItem>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(List<NewsItem> newsList) {
//Attached the final newslist to livedata
newsLiveData.setValue(newsList);
}
});
EDITED Updated the method
Object[] objects is the array of items returned by the requests. Assuming that each of your request returns a List<NewsItem> and you wanted to combine all NewsItem into a single List<NewsItem>, I think you can do something along the lines of:
private void loadNews(ArrayList<String> mySourceList) {
...
// Zip all requests
Observable.zip(requests, new Function<Object[], List<NewsItem>>() {
#Override
public List<NewsItem> apply(Object[] objects) throws Exception {
List<NewsItem> combinedNewsItems = new ArrayList<>();
for (Object response : objects) {
combinedNewsItems.addAll((List<NewsItem>) response);
}
return combinedNewsItems;
}
})
.subscribeOn(Schedulers.io())
...
}
Be aware of the type casting.
If the type of data that you are getting is same List and the requests are multiple then you can use Recursion Method to make 1 to n requests and add data List on every success.
Related
I am getting a Null Pointer Exception. I want to retrieve a random quote from this Quotes Api - https://quotes.rest/ to display in my android application. Where am I going wrong?
(I apologise if I had not asked the question properly or violated any rules by posting a question. This is my first time asking a question)
I have created necessary POJOs and tried retrieving the quote in the MainActivity.
GetDataService service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
Call<Example> call = service.getRandomQuote();
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(Call<Example> call, Response<Example> response) {
String quote = response.body().getContents().getQuotes().get(0).getQuote();
Log.d(TAG, "onResponse: ************" + quote);
}
GetDataService
public interface GetDataService {
#GET("/quote/random")
Call<Example> getRandomQuote();
}
POJO- Quote
public class Quote {
#SerializedName("quote")
private String quote;
public Quote(String quote) {
this.quote = quote;
}
public String getQuote() {
return quote;
}
public void setQuote(String quote) {
this.quote = quote;
}
}
POJO- Contents
public class Contents {
#SerializedName("quotes")
#Expose
private List<Quote> quotes;
public List<Quote> getQuotes() {
return quotes;
}
public void setQuotes(List<Quote> quotes) {
this.quotes = quotes;
}
}
POJO- Example
public class Example {
#SerializedName("contents")
#Expose
private Contents contents;
public Contents getContents() {
return contents;
}
public void setContents(Contents contents) {
this.contents = contents;
}
}
RetrofitClientInstance class
public class RetrofitClientInstance {
private static Retrofit retrofit = null;
private static final String BASE_URL = "https://quotes.rest";
public static Retrofit getRetrofitInstance(){
if(retrofit == null){
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
I expect the output to be a random quote from this Quotes Api (https://quotes.rest/) but I am getting a Null Pointer Exception. Following is the error-
java.lang.NullPointerException: Attempt to invoke virtual method 'com.platinumstudio.contactsdemo7.network.Contents com.platinumstudio.contactsdemo7.Example.getContents()' on a null object reference
at com.platinumstudio.contactsdemo7.MainActivity$3.onResponse(MainActivity.java)
The API that you're using here is this. As the API documentation says, an API key is required to fetch data. Since you didn't add an API key in your codes, it's should return an unsuccessful response with the HTTP Status Code of 401.
And it's always the best practice to check if the response is successful before proceeding to execute operations on received data to avoid crash issues in the app. Always keep this code snippet when you're fetching data from an API:
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(Call<Example> call, Response<Example> response) {
if(response.isSuccessful && response.body() != null){
//Get contents & quote
}
else {
//Check response code & show the relevant error message through Toast or Dialog
}
}
#Override
public void onFailure(Call call, Throwable t) {
//Show a failed message to the user
}
}
I believe that you need to add Api key to your request, otherwise you'll not be allowed to access the data. Hence, your response's body is null, which causes the crash. As it says on the website:
For using the private end points and subscribing to the API please visit https://theysaidso.com/api.
https://theysaidso.com/api/
From the API website you will find the link to access the ramdom quote
here is the link:
http://quotes.rest/quote/random.json?api_key=
without the key you will get Unauthorized access
This API requires Authorization key to access the data. Hence, modify your interface GetDataService like below :
public interface GetDataService {
#GET("/quote/random/")
Call<Example> getRandomQuote(#HeaderMap Map<String, String> headerMap);
}
//HeaderMap will have authorization key and its value as <Key,Value> pair.
I created an Observable(RxJava2 + Volley) that repeat for each 5 seconds,
It works but when I Dump Java Heap(memory),there are many Instance of my Model JAVA class,and it will increase for each time that the Observable get repeating.
Why RX create several instance of my model? How can I use only ONE instance of it?
Model
public RequestFuture<String> getLiveRefreshFuture() {
RequestFuture<String> future = RequestFuture.newFuture();
VolleyStringRequest request = new VolleyStringRequest(Request.Method.POST
, REFRESH_URL
, future
, future) {
#Override
protected Map<String, String> getParams() throws AuthFailureError {
return getRefreshParams();
}
};
VolleySingleton.getInstance().addToRequestQueue(request);
return future;
}
Activity
private final CompositeDisposable disposables = new CompositeDisposable();
final LiveRemoteModel model = DaggerLiveComponent.builder().build().getModel();
Observable<LiveResponse> observable = Observable
.interval(Constants.TOOLBAR_BADGES_REFRESH_DELAY, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.map(dummy -> model.getLiveRefreshFuture())
.map(RequestFuture::get)
.map(LiveResponse::new)
.observeOn(AndroidSchedulers.mainThread());
DisposableObserver<LiveResponse> disposableObserver =
new DisposableObserver<LiveResponse>() {
#Override
public void onNext(#NonNull LiveResponse liveResponse) {
setToolbarBadges(liveResponse.getToolbarBadges());
}
public void onError(#NonNull Throwable e) {
Log.e("RX", "onError: ", e);
}
#Override
public void onComplete() {
Log.d("RX", "onComplete: ");
}
};
disposables.add(observable.subscribeWith(disposableObserver));
Why RX create several instance of my model? How can I use only ONE instance of it?
If you look carefully the object in the heapdump is LiveRemoteModel$2 which indicates it is an anonymous class within LiveRemoteModel.
Looking at your code this is probably the VolleyStringRequest object that gets created each time model.getLiveRefreshFuture() is called. There is nothing retaining that object within the RX pipeline so there must be something within Volley retaining it.
(Android/Java professional, RxJava/Lambda novice) I'm trying to create the following pojo:
public class ProductCombinedPojo {
private ProductPojo product;
private List<TemplatePojo> templates;
// builder pattern...
}
where ProductPojo is:
public class ProductPojo {
private List<String> templateUrls; // each url returns a TemplatePojo
// lots of other stuff...
}
I have the following Retrofit2 implementations:
#GET
Observable<ProductPojo> getProduct(#Url String url);
#GET
Observable<TemplatePojo> getTemplate(#Url String url);
So the first Observable returns the ProductPojo, the resulting list uf urls within are iterated over and input to the second Observable to get the list of TemplatePojos, finally the results are all combined into a new ProductCombinePojo using a builder pattern. To further complicate matters, due to the nature of the MVP framework, this all has to be done in a single Func0<<Observable<ProductCombinedPojo>> chained RxJava implementation.
I'm having difficulty at the end of the chain cleanly getting the original ProductPojo to inject into the builder. Here is my working but ugly solution (assume mUrl, mProductApi & mTemplateApi are all injected and defined as above):
#Override
public Observable<ProductCombinedPojo> call() {
final ProductPojo[] aProductPojo = new ProductPojo[1]; // <------------ Ugly!
return mProductApi
.getProduct(mUrl)
.flatMapIterable(new Func1<ProductPojo, List<String>>() {
#Override
public List<String> call(ProductPojo productPojo) {
aProductPojo[0] = productPojo; // <------------ Ugly!
return productPojo.getTemplateUrls();
}
})
.flatMap(new Func1<String, Observable<TemplatePojo>>(){
#Override
public Observable<TemplatePojo> call(String templateUrl) {
return mTemplateApi.getTemplate(templateUrl);
}
})
.toList()
.map(new Func1<List<TemplatePojo>, ProductCombinedPojo>() {
#Override
public ProductCombinedPojo call(List<TemplatePojo> templatePojos) {
return ProductCombinedPojo.Builder.aProductCombinedPojo()
.product(aProductPojo[0]) // <------------ Ugly!
.templates(templatePojos)
.build();
}
});
}
How do I rewrite this so that I don't need the ugly final ProductPojo[]? After exhuastive searching and reviewing many similar questions on this forum, I think a
Func2<ProductPojo, List<TemplatePojo>, ProductCombinedPojo>
should be plugged in somewhere but I can't figure out exactly where. Whilst I am interested in what a Lambda solution will look like, the correct answer will be awarded to any solution using the format above.
The problem is that with every operation like map or flatMap, you transform the input into a new output. If you don't include the input in the output, you won't be able to access that input later on.
This is what you're facing here: you want to be able to access the ProductPojo further down the stream.
You could circumvent this by returning a Pair<ProductPojo, List<String>> in your flatMapIterable function, but this doesn't get any better either.
Instead you can create a new Observable in the scope of your ProductPojo:
public Observable<ProductCombinedPojo> call() {
return mProductApi.getProduct(mUrl)
.flatMap(new Func1<ProductPojo, Observable<ProductCombinedPojo>>() {
#Override
public Observable<ProductCombinedPojo> call(ProductPojo productPojo) {
return combinedPojoFor(productPojo);
}
});
}
private Observable<ProductCombinedPojo> combinedPojoFor(final ProductPojo productPojo) {
return Observable.from(productPojo.getTemplateUrls())
.flatMap(new Func1<String, Observable<TemplatePojo>>() {
#Override
public Observable<TemplatePojo> call(String templateUrl) {
return mTemplateApi.getTemplate(templateUrl);
}
})
.toList()
.map(new Func1<List<TemplatePojo>, ProductCombinedPojo>() {
#Override
public ProductCombinedPojo call(List<TemplatePojo> templatePojos) {
return ProductCombinedPojo.Builder.aProductCombinedPojo()
.product(productPojo)
.templates(templatePojos)
.build();
}
});
}
Using lambdas:
public Observable<ProductCombinedPojo> call() {
return mProductApi.getProduct(mUrl)
.flatMap((productPojo) -> combinedPojoFor(productPojo));
}
private Observable<ProductCombinedPojo> combinedPojoFor(final ProductPojo productPojo) {
return Observable.from(productPojo.getTemplateUrls())
.flatMap((templateUrl) -> mTemplateApi.getTemplate(templateUrl))
.toList()
.map((templatePojos ->
ProductCombinedPojo.Builder.aProductCombinedPojo()
.product(productPojo)
.templates(templatePojos)
.build()
));
}
I have a list of Observables like so:
List<Observable<MyObj>> listObservables = new ArrayList<Observable<MyObj>>();
I'd like to combine all Observable in a single one, I can handle it if I know the number of Observable using zip(), for example we have 3 Observable:
Observable<MyObj1> obs1= MyRestClient.getSomeData1();
Observable<MyObj2> obs2= MyRestClient.getSomeData2();
Observable<MyObj3> obs3= MyRestClient.getSomeData3();
I have a wrapper obj:
class MyWrapperObj {
private MyObj1 onj1;
private MyObj2 onj2;
private MyObj3 onj3;
public MyWrapperObj(MyObj1 onj1, MyObj2 onj2, MyObj3 onj3) {
this.onj1 = onj1;
this.onj2 = onj2;
this.onj3 = onj3;
}
}
So I can combine them like so:
Observable<MyWrapperObj> combinedObservable = Observable.zip(obs1, obs2, obs3, new Func3<MyObj1, MyObj2, MyObj3, MyWrapperObj>() {
#Override
public MyWrapperObj call(MyObj1 obj1, MyObj2 obj2, MyObj3 obj3) {
return new MyWrapperObj(obj1, obj2, obj3);
}
});
combinedObservable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<MyWrapperObj>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
}
#Override
public void onNext(MyWrapperObj wrapperObj) {
}
});
Everything is working fine, so my problem is how to organize this combination to be for n Observable?
RESPONSE
as #maciekjanusz mentioned in it's answer I did:
Observable<MyWrapperObj> combinedObservable = Observable.zip(listObservables, new FuncN<MyWrapperObj>() {
#Override
public MyWrapperObjcall(Object... args) {
return null;
}
});
If you want to zip n Observables, put them in a list and apply the public static <R> Observable<R> zip(#NotNull java.lang.Iterable<? extends Observable<?>> ws, rx.functions.FuncN<? extends R> zipFunction) factory method.
List<Observable<String>> observables = Arrays.asList(Observable.just("String 1"), Observable.just("String 2"));
Observable.zip(observables, args -> {
// put your zipping code here
});
For example, if you want to create a list of strings for each emission from all observables:
Observable.zip(observables, Arrays::asList);
Or, if using RxJava on android without retrolambda:
Observable.zip(observables, args -> Arrays.asList(args));
Suppose you have the list:
List<Observable<MyObj>> listObservables
You might consider using Observable.concatDelayError
The advantage if it is finishing all Obbservable's even if any of them finishes with error (resulting in an error in such case).
Remember, that every Observable in this sequence must return the result to onNext method of Subscriber. The result also must have the same type.
Example:
Observable.concatDelayError(listObservables);
You can wait until all observables is complete by using
.zip(observable1, ..., observableN, funcN).first() operators. There is an overload, accepting Observable> argument (as in FlatMap).
First overload takes Iterable> - you can pass list of observables of arbitrary size and second argument - FuncN - receives list of values.
I am getting a list of multimedia information as part of my API call using RxJava's map function to create a list of image URLs from the response JSON. How can I handle the returned list from the map function? This is my code:
BrowseAPI service = CKApplication.getInstance().getRestAdapter().create(BrowseAPI.class);
BrowseRequest request = new BrowseRequest("0","50","true","A");
service.getMultmedia(MyApplication.getInstance().getJSessionID(), request).map(new Func1<Multimedia, Object>() {
#Override
public Object call(Multimedia multimedia) {
ArrayList<String> imageURLs = new ArrayList<String>();
for(MultiMediaImage image : multimedia.getResultsWrapper().getMultiMediaImage()) {
String url = "https://xxxxxxx/service/content/imageByEntitlement/";
url += image.getEid();
url +="?thumbnail=true";
imageURLs.add(url);
}
return imageURLs;
}
}).subscribe(multimedia -> {
Log.d("dfa", "adga");
});
I am able to create the string list of URLs from the response, I would like to handle this list of URLs in a separate activity or fragment.
Using flatMap, I am sure this can be improved:
BrowseAPI service = CKApplication.getInstance().getRestAdapter().create(BrowseAPI.class);
BrowseRequest request = new BrowseRequest("0","50","true","A");
service.getMultmedia(CKApplication.getInstance().getJSessionID(), request).flatMap(new Func1<Multimedia, Observable<MultiMediaImage>>() {
#Override
public Observable<MultiMediaImage> call(Multimedia multimedia) {
Observable<MultiMediaImage> observable = Observable.from(multimedia.getResultsWrapper().getMultiMediaImage());
return observable;
}
}).subscribe(new Action1<MultiMediaImage>() {
#Override
public void call(MultiMediaImage multiMediaImage) {
service.getImageObject(CKApplication.getInstance().getJSessionID(), multiMediaImage.getEid()).observeOn(AndroidSchedulers.mainThread()).subscribe(imageObject -> {
mImages.add(imageObject.getAuthenticatedStoreUrl());
adapter.notifyDataSetChanged();
});
}
});
How about this?
Observable<Multimedia> multimediaObservable = service.getMultmedia(MyApplication.getInstance().getJSessionID(), request);
Subscription subscription = bindFragment(this, multimediaObservable
.observeOn(AndroidSchedulers.handlerThread(new Handler()))
.map(new Func1<Multimedia, ArrayList<String>>() {
#Override
public ArrayList<String> call(Multimedia multimedia) {
ArrayList<String> imageURLs = new ArrayList<String>();
for(MultiMediaImage image : multimedia.getResultsWrapper().getMultiMediaImage()) {
imageURLs.add(makeImageUrl(image);
}
return imageURLs;
}
})).subscribe(multimediaArrayList -> {
//do something with the list
});
...
private String makeImageUrl(MultiMediaImage image){
String url = "https://xxxxxxx/service/content/"
url += image.getEid();
url +="?thumbnail=true";
return url;
}
Or with Java8 syntax that you use in the subscribe() method:
.map(multimedia -> {
ArrayList<String> imageURLs = new ArrayList<String>();
for(MultiMediaImage image : multimedia.getResultsWrapper().getMultiMediaImage()) {
imageURLs.add(makeImageUrl(image);
}
return imageURLs;
})
Some notes:
I assume that the multimediaObservable is a network call which should not be done in the main thread. If its an observable emitted from Retrofit you don't need to worry to have a separate async thread.
The bindFragment() is part of the RxAndroid library which automatically
"observe(s)On" the mainThread(). For activities use bindActivity(). Alternatively use observeOn(mainThread()).
There is no need as you do in the second example to use flatmap because it converts without need the object to observable and back to normal object (which the map directly can receive).
Don't forget the onError in the subsribe().
Update:
You can use from() to flatten the array and handle per MultimediaImage item in the subscribe.
Subscription subscription = bindFragment(this, multimediaObservable
.observeOn(AndroidSchedulers.handlerThread(new Handler()))
.flatMap(multimedia->
Observable.from(multimedia.getResultsWrapper().getMultiMediaImage())
).subscribe(multimediaImage -> {
//multimediaImage is a MultimediaImage class object
Log.i("ImageId", multimediaImage.getEid());
});