1
votes

Comment faire plusieurs demandes en utilisant retrofit2 et rxjava2 sur Android?

Dans mon application Android, je souhaite effectuer plusieurs requêtes http à l'aide de retrofit et de rxjava pour récupérer des données json. Le nombre de requêtes dépend des préférences de l'utilisateur (1 jusqu'à 40). Chaque requête est indépendante et renvoie le même type. J'ai donc essayé d'appliquer la manière recommandée dans cette question ( Comment faire plusieurs requêtes et attendre que les données proviennent de toutes les requêtes de retrofit 2.0 - android ) qui utilise la fonction zip de rx-java. Mais je n'ai pas trouvé de moyen d'obtenir et de combiner les résultats de chaque demande. Le type de réponse que j'ai utilisé pour une seule demande lors de la modernisation était Response> où NewsItem est mon objet personnalisé. (la réponse est en fait le tableau json, mais en une seule demande, la modernisation le gère automatiquement et le convertit en liste de mon objet personnalisé) Ce que j'ai essayé jusqu'à présent est ci-dessous:

Mon interface API

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

    }
}


0 commentaires

3 Réponses :


0
votes

Object [] objects est le tableau des éléments retournés par les requêtes. En supposant que chacune de vos demandes renvoie une List et que vous vouliez combiner tous les NewsItem en une seule List , je pense vous pouvez faire quelque chose comme:

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())
        ...
}

Soyez conscient de la conversion de type.


4 commentaires

Merci @Sanlok, j'ai essayé mais je n'ai pas fonctionné. Mais cela me donne une idée de la façon dont je peux remplir l'intérieur de la méthode. De plus, le casting que vous suggérez donne l'alerte que Unchecked cast: 'java.lang.Object' vers 'java.util.List Je pense que le problème est lié au type de réponse. Dans une seule demande que j'ai faite avec Retrofit, la retrofit génère sa réponse brute, qui est de type Response> , puis vous pouvez la convertir en List en utilisant la méthode response.body. Mais maintenant je ne sais pas quoi faire?


Pouvez-vous nous dire ce que vous entendez par «cela n'a pas fonctionné»? Cela s'est-il écrasé? Avez-vous essayé de vous connecter?


De plus, je ne suis pas sûr de ce que vous entendez par Response> car votre "My API Interface" indique qu'il renvoie une Observable> . Pouvez-vous publier votre code mis à jour?


Désolé pour le malentendu, dans un seul appel d'API (que j'ai utilisé auparavant), bien sûr, l'interface de l'API est la suivante: Call> . Je veux dire que la réponse que la modernisation retourne est au type Response> qui peut être le facteur clé. Il ne s'est pas écrasé lorsque j'ai essayé votre réponse mais rien n'a changé.



0
votes

Si le type de données que vous obtenez est le même Liste et que les demandes sont multiples, vous pouvez utiliser la méthode de récursivité pour faire 1 à n requêtes et ajouter des données List à chaque succès.


1 commentaires

Merci, peut-être, mais comme je comprends de votre suggestion, ce ne serait pas parallèle, ce qui augmente le temps de retour.



1
votes

essayez d'utiliser Observable.from (Iterable extend T> iterable) ( Observable.fromArray () dans rx-java2) au lieu de zip Vous aurez donc quelque chose comme:

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 Mise à jour de la méthode


8 commentaires

merci @Boris mais en fait je suis nouveau dans RxJava, il vaudrait mieux que vous réécriviez, et mon studio Android ne prend pas en charge les lambas.


dans le cas de Rx-java2, vous pouvez utiliser Flowable.fromIterable (mySourceList) au lieu de Observable.from (mySourceList) :)


J'ai mis à jour la méthode avec votre aide (j'ai résolu le problème de la bibliothèque rx-java) Mais maintenant, lorsque je débogue, je vois qu'elle n'exécute pas la méthode .map, donc ne fonctionne toujours pas.


@HuseyinSahin êtes-vous sûr de .unsubscribe () ? Pour moi, il semble étrange de se désinscrire immédiatement après s'être abonné. Deuxième question: êtes-vous sûr que toutes les requêtes api.getGundemNews (source) ont été exécutées? Cause .toList attend l'exécution de tous les éléments


Je l'ai mis plus tard, cela ne fonctionnait pas avant non plus (quand livingata prend les éléments requis, il est sûr de se désinscrire je pense) mais ok, je l'ai supprimé.


oui, une désinscription est nécessaire, mais quelque part plus tard, lorsque vous fermez la vue par exemple


Je ne suis pas sûr que toutes les requêtes api.getNews (source) soient exécutées, je ne sais pas comment le vérifier mais une autre observation est que la fonction d'appel (sous le flatmap) est exécutée (elle entre dans la méthode) mais quand je me connecte la liste des sources là-dedans, cela ne m'a donné qu'une


Continuons cette discussion dans le chat .