• 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Tips & Tricks pour améliorer votre code

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Ce cours étant terminé, je vous propose ici d'améliorer la qualité de votre code, en vous donnant quelques trucs et astuces qui vous aideront à rendre celui-ci le plus propre et clair possible.

N'oubliez pas, pour rester un super développeur, qu'une de vos principales préoccupations sera de vous soucier constamment et au maximum de la qualité de votre code.  ;)

Et comme toujours, si vous ne devez lire qu'un seul livre dans votre vie de développeur, c'est bien celui-là : Coder proprement, de Robert C Martin (la version anglaise est plus facilement trouvable).

Il existe sur Android des librairies que vous devez absolument connaître, car ces dernières vous permettront de vous simplifier un maximum la vie. Nous allons ici en voir deux :

  • IcePick : Pour gérer plus simplement la manipulation de vos bundles

  • ButterKnife : Pour faciliter la déclaration de vos vues dans votre activité

A travers ce chapitre, nous nous servirons de notre mini-application déjà créée dans la deuxième partie de ce cours, à savoir MyFragmentApp, que vous trouverez en téléchargement à ce lien.

Icepick : Gérer plus facilement vos bundles

Reprenons ensemble notre application, MyFragmentApp. Jusqu'à maintenant, pour gérer la sauvegarde de données dans notre bundle, nous devions redéfinir les méthodes  onSaveInstanceState  et  onRestoreInstanceState  et coder nous même la persistance des données...

Voici par exemple le code du fragment DetailFragment.java qui gère actuellement cette sauvegarde :

public class DetailFragment extends Fragment {
    
    private int buttonTag;
    
    ...
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //Restore last buttonTag if possible
        if (savedInstanceState != null) {
            int buttonTagRestored = savedInstanceState.getInt(KEY_BUTTONTAG, 0);
            //Update TextView
            this.updateTextView(buttonTagRestored);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //Save buttonTag in Bundle when fragment is destroyed
        outState.putInt(KEY_BUTTONTAG, buttonTag);
    }
    
    ...
    
}

Maintenant, si je vous disais qu'avec IcePick on peut gérer cela BEAUCOUP plus facilement ? ;)

Allez c'est parti, je vous montre !

Tout d'abord, nous allons installer la librairie IcePick, disponible à ce lien, en utilisant notre gestionnaire de dépendances Gradle. Pour cela, modifiez les fichiers build.gradle de votre application MyFragmentApp :

allprojects {
    repositories {
        jcenter()
        maven {url "https://clojars.org/repo/"}
    }
}
dependencies {
  compile 'frankiesardo:icepick:{{latest-version}}'
  provided 'frankiesardo:icepick-processor:{{latest-version}}'
}

Une fois l'installation terminée, nous allons modifier notre classe DetailFragment.java afin d'implémenter IcePick :

public class DetailFragment extends Fragment {
    
    ...
    
    // 1 - Adding @State annotation to indicate to Icepick to save it
    @State int buttonTag;
    
    ...
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // 2 - Restore all @State annotation variables in Bundle
        Icepick.restoreInstanceState(this, savedInstanceState);
        this.updateTextView(this.buttonTag);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // 3 - Save all @State annotation variables in Bundle
        Icepick.saveInstanceState(this, outState);
    }
    
    ...

}

Explications : Nous allons ici dire à IcePick de s'occuper à notre place du support de notre bundle :

  • Ligne 1 : En ajoutant l'annotation @State, nous disons à Icepick d'enregistrer cette variable dans le bundle. Pensez simplement à retirer le 'private' devant la variable.

  • Ligne 2 et Ligne 3 : Nous plaçons une instance de IcePick, afin qu'il s'exécute à chaque fois que nos fameuses méthodes pour gérer notre bundle sont appelées. 

Lancez maintenant votre application (en mode tablette, rappelez-vous de notre problématique de la fin de la deuxième partie... ;) ).

Nous gérons maintenant la sauvegarde de nos données dans le Bundle les doigts dans le nez ! Si par exemple demain, vous deviez sauvegarder deux variables de plus, vous n'auriez plus qu'à écrire :

public class DetailFragment extends Fragment {
    
    ...
    
    @State String nameToSaveInBundle;
    @State String titleToSaveInBundle;
    
    ...
    
}

ButterKnife : Liez plus facilement vos vues à votre activité

Jusqu'à maintenant, afin de récupérer les vues dans notre activité et les sérialiser (les transformer en un objet JAVA), nous devions utiliser la triste méthode  findViewById(R.id.your_view).

Cela peut s'avérer long et occuper beaucoup d'espaces dans une classe, notamment lorsque l'on souhaite récupérer un nombre important de vues. C'est là que ButterKnife fait son entrée :). 

Afin de l'installer, vous devrez là aussi l'ajouter à votre fichier Gradle afin de créer une nouvelle dépendance :

dependencies {
    compile 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

Reprenons l'exemple de notre classe DetailFragment.java. Afin de récupérer et sérialiser notre TextView, nous faisions précédemment comme ce qui suit :

public class DetailFragment extends Fragment {

    // 1 - Declare TextView
    private TextView textView;
    
    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_detail, container, false);
        // 2 - Get and serialise textView from layout
        this.textView = (TextView) view.findViewById(R.id.fragment_detail_text_view);
        return(view);
    }
    
    ...
    
}

Imaginez maintenant que vous ayez 10 TextViews et 10 Boutons... Ça en ferait des lignes de code et des findViewById à écrire !

Avec ButterKnife, nous pouvons réduire drastiquement cette déclaration :

public class DetailFragment extends Fragment {

    // 1 - Adding @BindView in order to indicate to ButterKnife to get & serialise it
    @BindView(R.id.fragment_detail_text_view) TextView textView;
    
    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_detail, container, false);
        // 2 - Telling ButterKnife to bind all views in layout
        ButterKnife.bind(this, view);
        return(view);
    }
    
    ...
    
}

Explications : Nous allons dire à ButterKnife de créer à notre place les différents findViewByID, en accolant l'annotation @BindView à chaque vue que nous souhaitons récupérer depuis notre layout. Enfin, nous devons également ajouter une instance de ButterKnife dans notre onCreateView( ) afin d'activer ce processus de récupération (que l'on appelle aussi bind).

Vous verrez que vous gagnerez énormément de temps et rendrez votre code beaucoup plus lisible grâce à cette superbe librairie. Merci beaucoup Jake Wharton !  :honte: 

Pratiquez l'héritage de classe !

Un bon développeur est un développeur feignant. A travers les précédents chapitres de ce cours, vous avez dû vous rendre compte que nous répétions souvent les mêmes lignes de code.

Et ça, c'est un sacrilège ! :colere:

Si on reprend nos deux fragments de l'application MyFragmentApp, il faut pour chacun de nos fragments implémenter :

  • onCreateView( ) et ajouter à l'intérieur ButterKnife pour récupérer plus facilement nos vues

  • onActivityCreated( ) et onSaveInstanceState( ), en ajoutant à l'intérieur Icepick afin de gérer nos bundles

  • Ajouter potentiellement une méthode publique statique newInstance( ) nous permettant de normaliser la création de notre fragment

Cela représente pas mal de copier/coller, et d'une manière générale, les copier/coller sont à proscrire le plus souvent possible ! Vous devez toujours penser à réutiliser votre code, c'est un des buts ultimes de la programmation orientée objet :).

Ainsi, nous allons ici appliquer plusieurs concepts importants : l'héritage et les classes abstraites. Vous pouvez retrouver plus d'informations à leur sujet sur le cours dédié à JAVA (héritage et classes abstraites).

Pour faire simple, l'héritage consiste à récupérer automatiquement les propriétés et méthodes d'une classe parente, et d'une manière générale son comportement. Un peu comme vous quand vous héritez du sale caractère de vos parents... (Hein quoi ? Qui a dit ça ! :-°) 

Les classes abstraites sont quant à elles, des classes que l'on ne peut pas instancier (donc pas de new avec elles). Elles servent surtout à créer des classes parentes... ;)  

Nous allons donc ici créer une classe BaseFragment dont chacun de nos fragments héritera, afin d'éviter de répéter les mêmes lignes de code sur chaque fragment, les rendant ainsi plus lisibles :

Fichier BaseFragment.java :

public abstract class BaseFragment extends Fragment {

    // 1 - Force developer implement those methods
    protected abstract BaseFragment newInstance();
    protected abstract int getFragmentLayout();
    protected abstract void configureDesign();
    protected abstract void updateDesign();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 2 - Get layout identifier from abstract method
        View view = inflater.inflate(getFragmentLayout(), container, false);
        // 3 - Binding Views
        ButterKnife.bind(this, view);
        // 4 - Configure Design (Developer will call this method instead of override onCreateView())
        this.configureDesign();
        return(view);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // 5 - Handling Bundle Restoration
        Icepick.restoreInstanceState(this, savedInstanceState);
        // 7 - Update Design (Developer will call this method instead of override onActivityCreated())
        this.updateDesign();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // 6 - Handling Bundle Save
        Icepick.saveInstanceState(this, outState);
    }
}

Explications : Nous avons créé ici une classe abstraite BaseFragment, héritant de la classe Fragment. A l'intérieur, nous avons redéfini les méthodes les plus répétées au sein de MainFragment et DetailFragment :

  • Ligne 1 : Ces méthodes abstraites ont été créées pour être implémentées et surtout redéfinies dans les fragments héritant de BaseFragment. Pourquoi ? Tout simplement car elles sont utilisées juste en dessous, et que nous souhaitons forcer le développeur à les implémenter dans les futures classes enfants.

  • Ligne 2 : Au sein de la méthode onCreateView que nous avons redéfini, nous appelons la méthode abstraite getFragmentLayout( ). Celle-ci, déclarée dans nos fragments enfants, remontera l'identifiant du bon layout.

  • Ligne 3 : Toujours au sein de la méthode onCreateView que nous avons redéfini, nous lui ajoutons le support de ButterKnife. 

  • Ligne 4 : Enfin, nous appelons notre méthode abstraite configureDesign( ) ici. Pourquoi ? Dans le but de permettre à nos fragments enfants de ne pas avoir à redéfinir la méthode onCreateView( ), mais plutôt d'appeler à la place configureDesign( ), plus facile à retenir et à comprendre ;) .

  • Ligne 5 et 6 : Nous implémentons le support de Icepick pour gérer plus facilement nos bundles.

  • Ligne 7 : Sur le même model que configureDesign( ), nous appelons ici notre méthode updateDesign() pour permettre à nos fragments enfants de ne pas avoir à redéfinir la méthode onActivityCreated( ), mais plutôt d'appeler à la place updateDesign( ).

Effectuons maintenant la modification de notre fragment enfant DetailFragment, afin qu'il n'hérite maintenant plus de la classe Fragment mais plutôt de BaseFragment :

Fichier DetailFragment.java :

public class DetailFragment extends BaseFragment {

    @BindView(R.id.fragment_detail_text_view) TextView textView;
    @State int buttonTag;

    // --------------
    // BASE METHODS
    // --------------

    @Override
    protected BaseFragment newInstance() { return new DetailFragment(); }

    @Override
    protected int getFragmentLayout() { return R.layout.fragment_detail; }

    @Override
    protected void configureDesign() { }

    @Override
    protected void updateDesign() {
        this.updateTextView(this.buttonTag);
    }

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

    //Update TextView depending on TAG's button
    public void updateTextView(int tag){

        //Save tag in ButtonTag variable
        this.buttonTag = tag;

        switch (tag){
            case 10:
                this.textView.setText("You're a very good programmer !");
                break;
            case 20:
                this.textView.setText("I do believe that Jon Snow is going to die in next season...");
                break;
            case 30:
                this.textView.setText("Maybe Game of Thrones next season will get back in 2040 ?");
                break;
            default:
                break;
        }
    }
}

Explications : Comme vous pouvez déjà le remarquer, notre classe est devenue très petite et plus compréhensible non ? ^^ C'est là toute la magie de l'héritage et des classes abstraites !

Nous avons ici fait hériter DetailFragment de BaseFragment et avons donc été contraints (obligés par le mot clé abstract) d'implémenter les 4 méthodes abstraites.

Prenez le temps de lire et relire (voir encore relire !) cette partie, afin de bien assimiler et comprendre la logique derrière l'héritage et les classes abstraites.

Le déclic ne se fera peut-être pas tout de suite, ne vous inquiétez pas. Prenez votre temps ! Une graine aura déjà germé dans votre tête, et ça c'est le principal.

Vous trouverez l'application MyFragmentApp mise à jour à ce lien.

Conclusion

Bravo ! Mine de rien, vous venez d'accomplir quelque chose d'assez important dans la vie d'un développeur, à savoir l'amélioration de la qualité de son code, grâce à des librairies et concepts au top.

Encore félicitations !

                                                                        

Exemple de certificat de réussite
Exemple de certificat de réussite