• 20 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 12/12/19

Chainez différentes requêtes réseaux avec RxJava

Maintenant que nous en savons un peu plus sur la programmation réactive avec RxJava, nous allons poursuivre son implémentation dans notre application NetApp. Pour cela, nous allons ajouter notre précédente requête réseau dans une stream.

Ajouter une requête réseau dans une stream

Nous utilisons actuellement Retrofit pour exécuter notre appel réseau, et c'est une très bonne chose. En effet, Retrofit se combine parfaitement bien avec RxJava, grâce notamment à une petite libraire de support que nous allons installer dès maintenant !

Extrait du fichier build.gradle :

dependencies {
    ...
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
}

Puis, passons à la modification de notre code.

Extrait de GithubService.java :

public interface GithubService {
    
    @GET("users/{username}/following")
    Observable<List<GithubUser>> getFollowing(@Path("username") String username);

    public static final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build();
}

Explications : Nous modifions le fichier qui nous permet de lister les appels sur l'API de Github. Au lieu que notre appel réseau ne retourne une variable de type  Call<List<GithubUser>>  , il retournera dorénavant un  Observable<List<GithubUser>> que nous pourrons bien entendu, écouter ;). Nous ajoutons également un Adapter à l'instance Retrofit qui lui permettra de convertir automatiquement et plus facilement ses données en Observables.

Nous allons maintenant supprimer la classe GithubCalls qui ne nous sert plus à rien. A la place, nous créerons la classe GithubStreams qui nous permettra de répertorier toutes les streams relatives à Github.

Classe GithubStreams.java :

public class GithubStreams {
    
    public static Observable<List<GithubUser>> streamFetchUserFollowing(String username){
        GithubService gitHubService = GithubService.retrofit.create(GithubService.class);
        return gitHubService.getFollowing(username)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .timeout(10, TimeUnit.SECONDS);
    }
}

Explications : Cette classe va nous permettre de lister toutes les streams relatives à Github. Celles-ci retourneront toutes un Observable afin que nos contrôleurs (Fragments/Activités) puissent s'y inscrire (Subscriber) et récupérer leur émission de données.

Nous découvrons également des nouveaux opérateurs :

  • subscribeOn()  : Cet opérateur très utilisé sur Android va nous permettre d'exécuter l'Observable dans un Thread dédié (Schedulers.io), tout simplement ! 

    Plus besoin d'AsyncTask ou de HandlerThread pour réaliser des actions de manière asynchrones avec RxJava ! C'est quand même chouette ça non ? :p

  • observeOn()  : Cet opérateur également très utilisé sur Android va nous permettre de dire à tous les Subscribers d'écouter le flux de données de l'Observable sur le Thread Principal (AndroidSchedulers.mainThread). Ce qui nous permettra par la suite, de modifier des éléments de l'interface graphique depuis la méthode  onNext()  sans aucun soucis.

  • timeout()  : Ce dernier opérateur nous permettra d'indiquer un Timeout par défaut pour notre Observable. Si ce dernier n'a pas émit de données avant le temps définit (ici 10 secondes), l'émission de données sera coupée et une erreur de type Timeout sera envoyée au(x) Subscriber(s) via leur méthode  onError()  .

Maintenant que notre stream est créée, il va falloir y souscrire afin de la lancer. Eh bien c'est ce que nous allons faire maintenant depuis notre fragment MainFragment. D'ailleurs, nous allons faire du tri dans ce contrôleur, et ne garder que l'essentiel !

Classe complète MainFragment.java :

public class MainFragment extends Fragment {

    // FOR DESIGN
    @BindView(R.id.fragment_main_textview) TextView textView;

    //FOR DATA
    private Disposable disposable;

    public MainFragment() { }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.disposeWhenDestroy();
    }

    // -----------------
    // ACTIONS
    // -----------------

    @OnClick(R.id.fragment_main_button)
    public void submit(View view) {
        // 2 - Call the stream
        this.executeHttpRequestWithRetrofit();
    }

    // -------------------
    // HTTP (RxJAVA)
    // -------------------

    // 1 - Execute our Stream
    private void executeHttpRequestWithRetrofit(){
        // 1.1 - Update UI
        this.updateUIWhenStartingHTTPRequest();
        // 1.2 - Execute the stream subscribing to Observable defined inside GithubStream
        this.disposable = GithubStreams.streamFetchUserFollowing("JakeWharton").subscribeWith(new DisposableObserver<List<GithubUser>>() {
            @Override
            public void onNext(List<GithubUser> users) {
                Log.e("TAG","On Next");
                // 1.3 - Update UI with list of users
                updateUIWithListOfUsers(users);
            }

            @Override
            public void onError(Throwable e) {
                Log.e("TAG","On Error"+Log.getStackTraceString(e));
            }

            @Override
            public void onComplete() {
                Log.e("TAG","On Complete !!");
            }
        });
    }

    private void disposeWhenDestroy(){
        if (this.disposable != null && !this.disposable.isDisposed()) this.disposable.dispose();
    }

    // -------------------
    // UPDATE UI
    // -------------------

    private void updateUIWhenStartingHTTPRequest(){
        this.textView.setText("Downloading...");
    }

    private void updateUIWhenStopingHTTPRequest(String response){
        this.textView.setText(response);
    }

    private void updateUIWithListOfUsers(List<GithubUser> users){
        StringBuilder stringBuilder = new StringBuilder();
        for (GithubUser user : users){
            stringBuilder.append("-"+user.getLogin()+"\n");
        }
        updateUIWhenStopingHTTPRequest(stringBuilder.toString());
    }
}

Explications : Nous avons fait un peu de tri, afin de nous concentrer que sur l'essentiel, à savoir notre stream RxJava qui va exécuter l'appel réseau.

  • Ligne 1 : Nous créons une méthode privée qui va contenir l'appel de notre stream, et surtout sa souscription et donc, son exécution.

    • Ligne 1.1 : Pour commencer, nous mettons à jour notre interface graphique afin d'indiquer à l'utilisateur qu'une requête réseau est en train de s'exécuter.

    • Ligne 1.2 : Nous appelons depuis la classe GithubStreams notre Observable qui va émettre les données JSON récupérées depuis l'API Github grâce à Retrofit. Nous y souscrivons en créant un Subscriber (DisposableObserver) et en plaçant la souscription générée dans la variable de classe  this.disposable  pour éviter tout risque de Memory Leaks.

    • Ligne 1.3 : Enfin, une fois les données JSON récupérées (la liste de GithubUser), nous mettons à jour notre interface graphique.

  • Ligne 2 : On n'oublie pas d'appeler cette requête au clic de l'utilisateur sur le bouton "Get datas from Github"

Lancez votre application NetApp et appuyez sur le bouton "Get Datas From Github". Super ! Nous avons réussi à placer notre requête réseau dans une stream et à l'exécuter sans aucun problème :).

Chainer deux appels réseau

Imaginons maintenant que vous souhaitiez une fois la précédente requête réseau terminée (et qui nous a permise de récupérer la liste des utilisateurs suivis par Jake Wharton), récupérer immédiatement et à la suite les détails du premier utilisateur en tête de cette liste.

Eh bien je créé une seconde stream à la suite de celle-ci non ?

Eh non ! Vous y êtes presque, mais vous réfléchissez encore de manière non réactive... ;) Mais je sais, cela est très dur au début !

En fait, nous allons simplement rajouter cette action à la stream existante, grâce à l'opérateur FlatMap()  étudié dans le précédent chapitre.

Avant cela il faut que nous créions la requête réseau qui va récupérer les détails d'un utilisateur sur l'API Github (par exemple pour récupérer les détails de l'utilisateur Jake Wharton, c'est cette URL qu'il faudra interroger : https://api.github.com/users/JakeWharton).

Ainsi, on modifie notre interface déclarant la liste des API Github avec lesquelles nous souhaitons communiquer.

Extrait de l'interface GithubService.java :

public interface GithubService {
    
    ...
    
    @GET("/users/{username}")
    Observable<GithubUserInfo> getUserInfos(@Path("username") String username);
    
    ...
}

Explications : Nous déclarons simplement ici une nouvelle API, de la même manière qu'on a pu le voir dans un précédent chapitre. Cependant, il faut que l'on créé un nouvel objet correspondant au JSON reçu, dont la structure semble avoir changée ! Je vous laisse le faire manuellement en utilisant bien sûr le site que vous connaissez déjà, JsonSchema2Pojo.

Extrait de la classe GithubUserInfo.java :

public class GithubUserInfo {

    @SerializedName("login")
    @Expose
    private String login;
    
    ...
    
    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }
    
    ...
}

Explications : Je vous ai simplement mis ici un extrait de la classe GithubUserInfo représentant le JSON récupéré via l'API.

Puis, nous allons créer dans notre fichier GithubStreams une stream spécialement dédiée à cet appel réseau pour ensuite, combiner ces deux streams en une seule... :magicien:

Extrait de la classe GithubStreams.java :

public class GithubStreams {
    
    ...

    // 1 - Create a stream that will get user infos on Github API
    public static Observable<GithubUserInfo> streamFetchUserInfos(String username){
        GithubService gitHubService = GithubService.retrofit.create(GithubService.class);
        return gitHubService.getUserInfos(username)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .timeout(10, TimeUnit.SECONDS);
    }

    // 2 - Create a stream that will :
    //     A. Fetch all users followed by "username"
    //     B. Return the first user of the list
    //     C. Fetch details of the first user
    public static Observable<GithubUserInfo> streamFetchUserFollowingAndFetchFirstUserInfos(String username){
        return streamFetchUserFollowing(username) // A.
                .map(new Function<List<GithubUser>, GithubUser>() {
                    @Override
                    public GithubUser apply(List<GithubUser> users) throws Exception {
                        return users.get(0); // B.
                    }
                })
                .flatMap(new Function<GithubUser, Observable<GithubUserInfo>>() {
                    @Override
                    public Observable<GithubUserInfo> apply(GithubUser user) throws Exception {
                        // C.
                        return streamFetchUserInfos(user.getLogin());
                    }
                });
    }
}

Explications : Nous créons une stream (  streamFetchUserInfos  ) retournant un Observable correspondant à notre appel réseau sur l'API de Github et qui émettra au format JSON les détails d'un utilisateur (  GithubUserInfo  ). Puis nous créons une seconde stream, qui combinera l'exécution de deux streams en une seule.

  • Ligne 1 : La stream (streamFetchUserInfos  ), sur le même modèle que la précédente stream que nous avons créée (  streamFetchUserFollowing  ), retourne un Observable diffusant les détails (  GithubUserInfo  ) d'un utilisateur récupérés sur l'API de Github grâce à Retrofit.

  • Ligne 2 : Cette stream combine à elle seule l'exécution des deux précédentes ! Les liaisons sont assurées comme d'habitude par l'objet Function. Cette combinaison nous permettra d'appeler uniquement cette stream dans notre fragment, pour plus de simplicité... :)

    • Ligne A : On exécute la première stream  streamFetchUserFollowing  récupérant les utilisateurs suivis par Jake Wharton, ce qui nous retourne bien entendu une liste d'utilisateur (  List<GithubUser>  ).

    • Ligne B : Une fois la liste d'utilisateurs récupérée, nous retournons uniquement le premier élément de cette liste, donc un utilisateur seulement (  GithubUser  ).

    • Ligne C : Enfin, à partir de cet utilisateur sélectionné arbitrairement, nous allons pouvoir récupérer ses détails en lançant à la suite la stream  streamFetchUserInfos  .

Nous appellerons ensuite cette stream  streamFetchUserFollowingAndFetchFirstUserInfos  dans notre fragment MainFragment.

Extrait de la classe MainFragment.java :

public class MainFragment extends Fragment {

    ...

    // -----------------
    // ACTIONS
    // -----------------

    @OnClick(R.id.fragment_main_button)
    public void submit(View view) {
        // 2 - Call the stream
        this.executeSecondHttpRequestWithRetrofit();
    }

    // -------------------
    // HTTP (RxJAVA)
    // -------------------

    ...

    private void executeSecondHttpRequestWithRetrofit(){
        this.updateUIWhenStartingHTTPRequest();
        this.disposable = GithubStreams.streamFetchUserFollowingAndFetchFirstUserInfos("JakeWharton").subscribeWith(new DisposableObserver<GithubUserInfo>() {
            @Override
            public void onNext(GithubUserInfo users) {
                Log.e("TAG","On Next");
                updateUIWithUserInfo(users);
            }

            @Override
            public void onError(Throwable e) {
                Log.e("TAG","On Error"+Log.getStackTraceString(e));
            }

            @Override
            public void onComplete() {
                Log.e("TAG","On Complete !!");
            }
        });
    }

    ...

    // -------------------
    // UPDATE UI
    // -------------------

    ...

    private void updateUIWithUserInfo(GithubUserInfo userInfo){
        updateUIWhenStopingHTTPRequest("The first Following of Jake Wharthon is "+userInfo.getName()+" with "+userInfo.getFollowers()+" followers.");
    }
}

Explications : Nous avons ici créé executeSecondHttpRequestWithRetrofit() , une méthode qui va exécuter la stream précédemment définie, puis mettre à jour notre interface graphique en modifiant le TextView grâce à la méthode updateUIWithUserInfo(). On pense également à modifier notre méthode  submit()  afin d'appeler cette fois-ci cette nouvelle stream quand l'utilisateur clique sur le bouton "Get Datas From Github".

Lancez maintenant votre application NetApp, et appuyez sur le bouton. Alors, content du résultat ? :p

                                   

Conclusion

Alors je sais, la programmation réactive est assez complexe à appréhender. Il semblerait même qu'elle représente un des paliers les plus durs à franchir pour un développeur Android...

Mais une fois que vous arriverez à la comprendre et à correctement l'utiliser, vous verrez, plus rien ne vous arrêtera, c'est une certitude ! ^^

Example of certificate of achievement
Example of certificate of achievement