2

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.

Huseyin Sahin
  • 211
  • 4
  • 16

3 Answers3

1

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

Huseyin Sahin
  • 211
  • 4
  • 16
borichellow
  • 1,003
  • 11
  • 11
  • thanks @Boris but actually i am new in RxJava, it would be better you rewrite, and also my android studio doesn't supports lambas. – Huseyin Sahin Jan 29 '19 at 20:13
  • 1
    in case of Rx-java2 you can use `Flowable.fromIterable(mySourceList)` instead of `Observable.from(mySourceList)` :) – borichellow Jan 31 '19 at 14:00
  • I've updated the method with the help of you (i've solved rx-java library problem) But now, when I debug, i see that it is not executing the .map method, so not working still. – Huseyin Sahin Jan 31 '19 at 15:26
  • @HuseyinSahin are you sure about `.unsubscribe()` at all? For me it looks strange to unsubscribe immediate after subscribe. Second question: are you sure that all `api.getGundemNews(source)` requests were executed? Cause `.toList` waits all items execution – borichellow Jan 31 '19 at 15:31
  • I've put it later on, it was not working before either (when livedata takes the required items, it is safe to unsubscribe i think) but ok, i've removed it. – Huseyin Sahin Jan 31 '19 at 15:32
  • yes, unsubscribe is needed, but somewhere later, when you close the view for example – borichellow Jan 31 '19 at 15:36
  • I'am not sure that all api.getNews(source) requests are executed, i don't know how to check it but another observation is the call function (under the flatmap) is executed (it enters the method) but when i log the source list in there, it gave me only one of them – Huseyin Sahin Jan 31 '19 at 15:43
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187670/discussion-between-boris-safonov-and-huseyin-sahin). – borichellow Jan 31 '19 at 15:56
0

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.

Sanlok Lee
  • 3,404
  • 2
  • 15
  • 25
  • Thanks @Sanlok, i've tried but didn't work. But it gives me some idea about how can I fill the method's inside. Also the casting that you suggest gives alert that `Unchecked cast: 'java.lang.Object' to 'java.util.List` I think the issue is related to the response type. In single request that i made with Retrofit, retrofit generates it's raw response, which is type `Response>`, then you can convert it to `List` by using response.body method. But now i don't know what to do? – Huseyin Sahin Jan 28 '19 at 08:46
  • Can you tell us what you meant by "it didn't work"? Did it crash? Have you tried logging? – Sanlok Lee Jan 29 '19 at 00:39
  • Also I am not sure what you meant by `Response>` because your "My API Interface" says it returns an `Observable>`. Can you post your updated code? – Sanlok Lee Jan 29 '19 at 00:41
  • Sorry for misunderstanding, in a single api call (which i used before) of course the api interface is this: `Call>`. i mean the response that retrofit returns is at type `Response>` which may be the key factor. It didn't crashed when i tried your answer but nothing changed. – Huseyin Sahin Jan 29 '19 at 20:11
0

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.

DeePanShu
  • 1,236
  • 10
  • 23