• 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

Perfectionnez votre code grâce la programmation réactive et RxJava

Introduction

Maintenant que vous savez créer des tâches asynchrones et y exécuter des appels réseau à l'intérieur, une limite risque de se poser assez naturellement.

En effet, imaginez un cas d'utilisation assez courant au sein d'une application Android. L'utilisateur clique sur un bouton, et une suite logique d'événements se produit, les uns à la suite des autres :

  1. Vous devez récupérer une ressource X sur une API

  2. Ensuite, vous devez afficher les détails de cette ressource X sur une seconde API

  3. Une fois les détails récupérés, vous devez les enregistrer sur le téléphone (BDD SQLite)

  4. Puis, vous devez mettre à jour l'interface graphique de l'utilisateur.

Imaginez l'implémentation de tous ces événements et le nombre de callback que vous allez devoir créer. On appelle ce phénomène le Callback Hell !

Représentation du phénomène de CallBack Hell

Tout cela risque d'être compliqué à maintenir sur le long terme, et les futurs développeurs qui liront votre code mettront beaucoup de temps à le comprendre. Les tests unitaires deviendront très problématiques car votre code contiendra trop de dépendances inter-classes, etc... Bref cela n'est pas le top !

                                                                   

Et chez Openclassrooms, on souhaite faire de vous un développeur Android AU TOP !

Qu'est-ce-que la programmation réactive ?

Pour résumer rapidement ce qu'est la programmation réactive, celle-ci se définit comme une manière de développer en nous invitant à réfléchir en terme de flux de données, que l'on appelle aussi des streams. 

Hein ? Mais qu'est-ce que tu veux dire par là ? :waw:

Eh bien maintenant, grâce à la programmation réactive, nous allons créer des flux (streams) de données exécutant de manière ordonnée des actions bien précises. 

Si l'on reprend notre exemple plus haut, en utilisant la programmation réactive, nous pourrons créer un seul et même flux de données réalisant toutes ces actions, les unes à la suite des autres, au sein d'une même méthode, en quelques lignes seulement. :D

Pour paraphraser Mark Hudnall, CTO chez Coinbase :

La programmation réactive n'est pas aussi compliquée qu'elle n'y parait. La programme réactive parle uniquement de flux et de données. Un flux créé des données à différents moments dans le temps. Un observateur sera quand à lui notifié quand ces données seront récupérées (ou si une erreur s'est produite), afin qu'il puisse réaliser une action spécifique avec celles-ci. 

La programmation réactive s'inspire énormément du pattern de conception (pattern design) Observateur que l'on retrouve sur Android par exemple, avec les Listeners... ;)

La programmation réactive avec RxJAVA

Comment implémenter la programmation réactive sur Android ?

Eh bien en nous penchant sur la librairie RxJava, réalisée par Netflix (oui oui, le service de VOD !). Ces derniers ont implémenté en Java le projet ReactiveX (pour Reactive Extensions) développé à l'origine par Microsoft en 2012, définissant toutes les spécifications et fonctionnalités de la programmation réactive.

ReactiveX se définit comme "Une API pour programmer de manière asynchrones grâce à des flux observables". Tiens tiens, asynchrones, ça me dit quelque chose ça... :p Et oui ! ReactiveX va nous permettre de créer encore plus facilement des tâches asynchrones !

De plus, il est important de noter que vous pouvez retrouver l'implémentation de ReactiveX dans n'importe quel autre langage de programmation comme RxSwift pour Swift (iOS) ou encore RxJs pour Javascript !

Les bases de ReactiveX

Il faut savoir qu'une stream (flux de données) en ReactiveX est principalement composée de deux éléments :

  • Un Observable : Il représente la source d'émission des données. Généralement, un observable commence à émettre ses données dès lors qu'un ou plusieurs Subscriber se mettent à l'écouter. Un observable termine son émission de données, soit avec succès, soit en renvoyant une erreur.

  • Un Subscriber (parfois appelé Observer) : Il représente celui qui va écouter l'Observable pour ainsi récupérer son émission de donnéesIl procède ainsi à son écoute en s'y inscrivant, tout simplement.

Vous pouvez vous représenter ces deux composants en pensant aux cours d'Openclassrooms (Observables) et à ses étudiants (Subscribers) : Les cours sont émis par Openclassrooms et les étudiants s'inscrivent aux cours pour les écouter. C'est tout. Le fonctionnement est aussi simple que cela ! :)

Observable / Subscribers
Observable / Subscribers

Sur Android, on peut imaginer qu'une requête réseau soit un Observable, et notre activité/fragment soit le Subscriber... ;)

Le rôle du Subscriber ne se limite pas à une simple écoute passive, bien au contraire ! Chaque Subscriber dispose obligatoirement de ces 3 méthodes servant à l'alerter :

  • onNext()  : Cette méthode sera appelée dès qu'un élément sera émis par l'Observable (dans notre exemple précédent, cette méthode sera appelée dès qu'un cours sera diffusé)

  • onComplete()  : Cette méthode sera appelée quand l'Observable aura terminé avec succès son émission de données (dans notre exemple précédent, cette méthode pourrait être appelée quand Openclassrooms n'aura plus de cours à diffuser)

  • onError()  : Cette méthode sera appelée si une erreur se produit lors de l'émission d'un élément par l'Observable (dans notre exemple précédent, cette méthode pourrait être appelée si un cours contient une erreur grave) 

Vous connaissez maintenant les bases, passons alors à un exemple concret !

Implémenter votre première stream

Reprenons notre application NetApp (vous pouvez également la retrouver à ce commit). Ouvrez-là et installez les packages dont vous aurez besoin pour commencer à programmer de manière réactive... ;)

Extrait du fichier build.gradle :

dependencies {
    ...
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'io.reactivex.rxjava2:rxjava:2.1.7'
}

Puis nous allons créer un Observable tout simple, qui va émettre seulement un String qui sera ensuite affiché dans un TextView. Notre fragment (qui jouera le rôle de Subscriber) s'inscrira à son émission.

Extrait de MainFragment.java :

public class MainFragment extends Fragment implements NetworkAsyncTask.Listeners, GithubCalls.Callbacks {
    
    ...
    
    // 4 - Declare Subscription
    private Disposable disposable;
    
    ...
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        this.disposeWhenDestroy();
    }
    
    // -----------------
    // ACTIONS
    // -----------------
    
    @OnClick(R.id.fragment_main_button)
    public void submit(View view) {
        this.streamShowString();
    }
    
    // ------------------------------
    //  Reactive X
    // ------------------------------
    
    // 1 - Create Observable
    private Observable<String> getObservable(){
        return Observable.just("Cool !");
    }

    // 2 - Create Subscriber
    private DisposableObserver<String> getSubscriber(){
        return new DisposableObserver<String>() {
            @Override
            public void onNext(String item) {
                textView.setText("Observable emits : "+item);
            }

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

            @Override
            public void onComplete() {
                Log.e("TAG","On Complete !!");
            }
        };
    }
    
    // 3 - Create Stream and execute it
    private void streamShowString(){
        this.disposable = this.getObservable()
                .subscribeWith(getSubscriber());
    }

    // 5 - Dispose subscription
    private void disposeWhenDestroy(){
        if (this.disposable != null && !this.disposable.isDisposed()) this.disposable.dispose();
    }

    ...
    
}

Explications : Dans nos premiers pas dans la programmation réactive, nous créons ici les 3 éléments indispensables à celle-ci, à savoir un Observable, un Subscriber et une Stream.

  • Ligne 1 :Création d'un Observable. Un observable peut se créer de plusieurs façons. J'ai souhaité ici vous montrer la manière la plus simple de le faire, grâce à la méthode  Observable.just()  qui va émettre le string "Cool !". Cependant, rappelez-vous, un observable commence à émettre uniquement lorsque l'on s'inscrit à lui.

    Pour reprendre l'exemple d'un cours, un professeur (Observable) ne va pas commencer le cours si aucun étudiant n'est présent (Subscriber)... ;)

  • Ligne 2 :Création d'un Subscriber. Nous créons ici un Subscriber en utilisant un DisposableObserver, qui est généralement le type de Subscriber que l'on utilise le plus souvent sur Android. Dans ce dernier nous allons faire en sorte qu'une fois un élément émis (donc ici le String "Cool !"), nous mettions à jour le TextView avec.

  • Ligne 3 :Création d'une Stream (Observable + Subscriber). C'est ici que nous allons créer une Stream qui va lancer l'émission de l'Observable. Pour cela nous appelons l'Observable en question, puis nous y inscrivons notre Subscriber précédemment configuré, avec la méthode subscribeWith().

    Vous remarquerez la variable this.disposable qui semble récupérer le résultat de la stream ? Eh bien en fait celle-ci récupère la preuve de l'enregistrement (la souscription), qui nous servira notamment à nous désinscrire de la stream afin de ne plus recevoir d'émission de la part de l'Observable.

  • Ligne 4 :Création d'un Disposable. Cette variable correspond à la souscription du Subscriber à l'Observable. Celle-ci nous permettra de nous désinscrire facilement de l'Observable lorsque nous souhaiterons ne plus recevoir son émission de données.

    Cela se fait notamment lorsque l'activité ou le fragment est détruit(e) afin d'éviter les problèmes de Memory Leaks (Exactement pareil qu'avec les Handlers par exemple, que l'on a vus dans le premier chapitre de ce cours).

  • Ligne 5 :Désinscription de l'Observable. Nous avons créé une méthode qui nous servira à nous désinscrire de l'Observable quand le fragment est détruit. Nous appelons cette méthode dans le  onDestroy()  de ce dernier.

Représentation d'une Stream
Structure d'une Stream en ReactiveX

N'oublions pas d'appeler notre première stream dans le listener submit() de notre bouton pour la déclencher au clic de l'utilisateur. Exécutez l'application et savourez votre première stream ! :D

Améliorer votre stream avec des opérateurs

ReactiveX vous fournit une liste d'opérateurs bien pratiques qui vous permettront d'affiner encore plus votre stream. Vous allez ainsi pouvoir chainer plusieurs actions les unes à la suite des autres, filtrer les résultats en fonction de certaines conditions, etc...

Nous allons maintenant voir les deux principaux : map() et flatMap().

L'opérateur Map

Selon la documentation, cet opérateur permet après l'émission d'un élément par un Observable, de le transformer grâce à une fonction.

C'est-à-dire ? :waw: Eh bien par exemple, après l'émission de notre String "Cool !", nous pourrions utiliser à la suite l'opérateur Map afin de mettre en majuscule le résultat ("COOL !").

Faisons-le d'ailleurs ! Pour cela, on va légèrement modifier notre stream actuelle, en lui ajoutant l'opérateur Map() qui exécutera une fonction permettant de mettre un String en majuscule.

Extrait de MainFragment.java :

public class MainFragment extends Fragment implements NetworkAsyncTask.Listeners, GithubCalls.Callbacks {
    
    ...
    
    // ------------------------------
    //  Reactive X
    // ------------------------------

    private void streamShowString(){
        this.disposable = this.getObservable()
                .map(getFunctionUpperCase()) // 2 - Apply function
                .subscribeWith(getSubscriber());
    }
    
    // 1 - Create function to Uppercase a string
    private Function<String, String> getFunctionUpperCase(){
        return new Function<String, String>() {
            @Override
            public String apply(String s) throws Exception {
                return s.toUpperCase();
            }
        };
    }
}

Explications : Nous avons rajouté ici une méthode,  getFunctionUpperCase(), qui nous permettra de mettre en majuscule un String. Puis nous avons appelé cette méthode après l'émission de l'Observable.

  • Ligne 1 : Nous avons créé une méthode getFunctionUpperCase() qui retourne un objet de type Function. Cette objet de type Function sert dans ReactiveX principalement à réaliser des liaisons entre différentes méthodes ou Observable au sein d'une Stream.

    Cet objet dispose de deux paramètres génériques (  Function<String, String>  ), le premier correspondant au type de la variable en entrée, le second au type de la variable retournée.

    Dans notre cas, nous souhaitons modifier une variable String (Entrée) pour la mettre en majuscule et la retourner (Sortie). Cette syntaxe est peu triviale je sais, mais vous verrez cela devient très vite naturel quand on pratique régulièrement !

  • Ligne 2 : Avec l'opérateur  map() , nous appelons notre méthode précédemment créée, qui va être exécutée après l'émission de données. 

Fonctionnement de l'opérateur Map( )
Fonctionnement de l'opérateur Map( )

Exécutez maintenant votre application et cliquez sur le bouton. Magique ! Le texte est maintenant en majuscule.

Ne commencez-vous pas à imaginer les possibilités de ce genre de chainage de méthodes ? :p

L'opérateur FlatMap

Selon la documentation, cet opérateur permet, après l'émission d'un élément par un Observable, de le transformer en un autre Observable émettant à son tour un ou plusieurs éléments.

C'est-à-dire ? :waw: Et bien nous allons grâce à cet opérateur, chainer ensemble plusieurs Observables, donc plusieurs sources d'émissions de données, les unes à la suite des autres. Nous allons pouvoir par exemple, ajouter à la source de données "Cool !", la source de données "I love OpenClassrooms !", afin d'obtenir au final "Cool ! I love OpenClassrooms !".

Faisons-le immédiatement. Pour cela, modifions notre fragment comme ce qui suit.

Extrait de MainFragment.java

public class MainFragment extends Fragment implements NetworkAsyncTask.Listeners, GithubCalls.Callbacks {
    
    ...
    
    // ------------------------------
    //  Reactive X
    // ------------------------------

    private void streamShowString(){
        this.disposable = this.getObservable()
                .map(getFunctionUpperCase())
                .flatMap(getSecondObservable()) // 2 - Adding Observable
                .subscribeWith(getSubscriber());
    }
    
    // 1 - Create a function that will calling a new observable
    private Function<String, Observable<String>> getSecondObservable(){
        return new Function<String, Observable<String>>() {
            @Override
            public Observable<String> apply(String previousString) throws Exception {
                return Observable.just(previousString+" I love Openclassrooms !");
            }
        };
    }
}

Explications : Nous avons créé une méthode appelant un nouvel Observable, puis nous l'avons ajouté à notre stream.

  • Ligne 1 : Nous créons une méthode qui retournera un objet Function prenant deux paramètres, un String en entrée, et un Observable<String> en sortie. Puis à l'intérieur de cet objet Function, nous appellerons un Observable qui diffusera un String ("I love OpenClassrooms !").

  • Ligne 2 : Enfin, nous appellerons cette méthode exécutant cet Observable dans notre Stream.

Fonctionnement de l'opérateur FlatMap( )
Fonctionnement de l'opérateur FlatMap( )

Lancez maintenant votre application et appuyez sur le bouton. Vous devriez voir apparaitre maintenant dans le TextView "COOL ! I love OpenClassrooms !".

Nous avons fait pour le moment tout cela en utilisant des sources d'émission relativement simples, à savoir l'émission d'un String. Cependant, nous pouvons placer dans un Observable absolument n'importe quoi, comme une requête réseau récupérant des données sur une API par exemple...

Eh bien c'est ce que nous allons faire dès le prochain chapitre ! :p

Example of certificate of achievement
Example of certificate of achievement