• 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 07/12/2020

Améliorez la stabilité de votre application

Maintenant que votre application est enfin optimisée sur tablette, nous allons un peu l'améliorer, notamment en lui ajoutant différents boutons et comportements supplémentaires.

Nous poursuivrons donc le développement de l'application MyFragmentApp disponible à ce lien.

Communication entre fragments

Afin de vous expliquer correctement la manière dont des données peuvent être passées au lancement d'un fragment, nous allons modifier l'interface graphique de notre fragment MainFragment afin de lui ajouter 3 boutons, qui auront pour objectif :

  • Bouton "SHOW HAPPY DETAIL" : afficher des détails joyeux dans le fragment DetailFragment 

  • Bouton "SHOW SAD DETAIL" : afficher des détails tristes dans le fragment DetailFragment

  • Bouton "SHOW HORRIBLE DETAIL" : afficher des détails horribles dans le fragment DetailFragment

Nous sommes d'accord, pour effectuer cela, il va falloir que nos fragments MainFragment et DetailFragment communiquent ensemble. Mais qui va donc s'occuper d'orchestrer cette communication ?  :o

Et bien c'est l'activité parente, MainActivity ou DetailActivity, en fonction du fait qu'on soit sur tablette ou smartphone.

Implémentation des boutons

Pour commencer, nous allons implémenter dans notre fragment MainFragment, les 3 boutons en question : 

Fichier fragment_main.xml :

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <Button
        android:id="@+id/fragment_main_button_happy"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Show Happy Detail"
        android:tag="10"/>

    <Button
        android:id="@+id/fragment_main_button_sad"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Show Sad Detail"
        android:tag="20"/>

    <Button
        android:id="@+id/fragment_main_button_horrible"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Show Horrible Detail"
        android:tag="30"/>

</LinearLayout>

Explications : Nous avons ici défini des boutons possédant des TAGS, afin de pouvoir les reconnaître depuis notre code JAVA, et ainsi n'avoir besoin de créer qu'un seul listener. Pas folle la guêpe... ;) 

Nous allons donc modifier notre fichier MainFragment afin d'enregistrer le listener sur ces nouveaux boutons créés :

Fichier MainFragment.java :

public class MainFragment extends Fragment implements View.OnClickListener {

    ....
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

         // Inflate the layout of MainFragment
        View result=inflater.inflate(R.layout.fragment_main, container, false);

        // Set onClickListener to buttons
        result.findViewById(R.id.fragment_main_button_happy).setOnClickListener(this);
        result.findViewById(R.id.fragment_main_button_sad).setOnClickListener(this);
        result.findViewById(R.id.fragment_main_button_horrible).setOnClickListener(this);

        return result;
    }
    
    ...
    
}

Nul besoin ici de modifier davantage le fichier MainFragment, car c'est MainActivity qui gèrera à sa place le comportement des boutons.

Ainsi, il nous faudra modifier MainActivity afin qu'il puisse récupérer et passer le TAG du bouton (enclenché lors d'un clic utilisateur) directement à notre fragment DetailFragment :

Extrait du fichier MainActivity.java :

 public class MainActivity extends AppCompatActivity implements MainFragment.OnButtonClickedListener {
     
    ....
     
    @Override
    public void onButtonClicked(View view) {
        // 1 - Retrieve button tag
        int buttonTag = Integer.parseInt(view.getTag().toString());
    
        // 2 - Check if DetailFragment is visible (Tablet)
        if (detailFragment != null && detailFragment.isVisible()) {
            // 2.1 - TABLET : Update directly TextView
            detailFragment.updateTextView(buttonTag);
        } else {
            // 2.2 - SMARTPHONE : Pass tag to the new intent that will show DetailActivity (and so DetailFragment)
            Intent i = new Intent(this, DetailActivity.class);
            i.putExtra(DetailActivity.EXTRA_BUTTON_TAG, buttonTag);
            startActivity(i);
        }
    }
    
    ....
    
}

Explications :

Nous avons ci-dessus complètement modifié notre méthode onButtonClicked( ). Le but étant de passer le TAG à notre fragment DetailFragment, il faut déjà savoir si ce dernier est accessible ou non.

En effet, comme nous l'avons vu dans le chapitre précédent, le fragment DetailFragment n'est disponible  au sein de MainActivity qu'en mode Tablette, au côté de MainFragment. En mode Smartphone, DetailFragment n'est disponible qu'à travers DetailActivity.

C'est donc ce que nous avons réalisé ci-dessus :

  • Ligne 1 : Nous récupérons le TAG contenu dans le bouton en question (10, 20 ou 30) permettant de l'identifier.

  • Ligne 2 : Nous vérifions si notre fragment DetailFragment est affiché (il l'est en mode Tablette, mais pas en mode Smartphone).

  • Ligne 2.1 : Si DetailFragment est affiché, nous appellerons une de ses méthodes publiques (que nous allons créer tout à l'heure) afin de mettre à jour son TextView.

  • Ligne 2.2 : Si DetailFragment n'est pas affiché, nous passerons le TAG directement à DetailActivity (via un intent) qui s'occupera alors de le passer à son fragment enfant, DetailFragment donc.

Il nous reste donc DetailFragment et DetailActivity à modifier si j'ai bien compris ?

Tout à fait ! Vous avez tout compris ;).

Fichier DetailFragment.java :

public class DetailFragment extends Fragment {

    // 1 - Declare TextView
    private TextView textView;

    public DetailFragment() { }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_detail, container, false);
        // 2 - Get textView from layout (don't forget to create ID in fragment_detail.xml)
        this.textView = (TextView) view.findViewById(R.id.fragment_detail_text_view);
        return(view);
    }

    // 3 - Update TextView depending on TAG's button
    public void updateTextView(int 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 : Le but ici est de récupérer le TextView et de le modifier en fonction du TAG qu'il reçoit :

  • Ligne 1 : Déclaration de notre TextView

  • Ligne 2 : On sérialise et récupère notre TextView depuis notre layout (n'oubliez pas d'ajouter un identifiant au TextView dans le layout fragment_detail.xml, ici fragment_detail_text_view).

  • Ligne 3 : On crée notre méthode avec une portée publique (afin que l'on puisse y accéder en dehors de la classe DetailFragment), qui va en fonction du TAG fourni en paramètre, modifier le texte du TextView.

Modifions maintenant notre activité DetailActivity.java :

Extrait de DetailActivity.java :

public class DetailActivity extends AppCompatActivity {

    // 1 - Create static variable to identify Intent
    public static final String EXTRA_BUTTON_TAG = "com.openclassrooms.myfragmentapp.Controllers.Activities.DetailActivity.EXTRA_BUTTON_TAG";

    ....

    @Override
    public void onResume() {
        super.onResume();
        // 3 - Call update method here because we are sure that DetailFragment is visible
        this.updateDetailFragmentTextViewWithIntentTag();
    }

    ....

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

    // 2 - Update DetailFragment with tag passed from Intent
    private void updateDetailFragmentTextViewWithIntentTag(){
        // Get button's tag from intent
        int buttonTag = getIntent().getIntExtra(EXTRA_BUTTON_TAG, 0);
        // Update DetailFragment's TextView
        detailFragment.updateTextView(buttonTag);
    }
}

Explications : Nous allons ici appeler la méthode publique updateTextView( ) du fragment DetailFragment qui nous permettra de mettre à jour le texte de son TextView : 

  • Ligne 1 : Nous allons créer une variable statique publique contenant un identifiant destiné à notre intent, plus facile à manipuler.

  • Ligne 2 : Nous avons créé une méthode qui nous permet de récupérer le TAG passé en intent depuis MainActivity vers DetailActivity, pour ensuite mettre à jour le TextView de DetailFragment en appelant sa méthode publique updateTextView( ).

  • Ligne 3 : Cette méthode est appelée dans onResume(), afin d'être sûr et certain que notre fragment DetailFragment soit correctement créé et affiché, avant de modifier son TextView.

Lancez maintenant votre application sur une tablette (mais aussi sur un smartphone) afin de vous rendre compte du résultat.

 

Gérer le Bundle d'un fragment

Vous avez surement dû le remarquer, mais lors de la rotation depuis un terminal de type tablette, notre TextView est à chaque fois remis à zéro. Zut !

Mais pourquoi cela ne se produit que sur les terminaux de type tablette ? La rotation ne remet pas à zéro mon TextView sur smartphone pourtant !

Bonne question ! Tout simplement car sur smartphone, le TAG du bouton est passé en extra d'un intent puis récupéré par l'activité DetailActivity pour enfin être envoyé à DetailFragment. Chose à savoir, lors de la destruction d'une activité (dans notre cas la rotation sur DetailActivity), les extras d'un intent ne sont pas supprimés... ;)

Sur tablette en revanche, comme le TAG du bouton est directement envoyé d'un fragment à un autre (en passant par l'activité), celui-ci n'est jamais sauvegardé.

Pour remédier à ce problème, nous allons enregistrer manuellement le TAG du bouton dans le Bundle du fragment DetailFragment, tout simplement.

public class DetailFragment extends Fragment {

    ....

    // 1 - Declare a buttonTag tracking
    private int buttonTag;
    // 2 - Create static variable to identify key in Bundle
    private static final String KEY_BUTTONTAG = "com.openclassrooms.myfragmentapp.Controllers.Fragments.DetailFragment.KEY_BUTTONTAG";

    ....

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

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

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

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

        // 3 - Save tag in ButtonTag variable
        this.buttonTag = tag;
        
        ....
        
    }
}

Explications : L'objectif affiché ici est de sauvegarder le TAG de notre bouton, à la fois dans notre classe, mais aussi dans le Bundle afin de le récupérer plus facilement lors de la rotation de notre terminal :

  • Ligne 1 : Nous déclarons une variable qui servira de tracker et mémorisera dans notre classe, le TAG d'un bouton passé en paramètre de la méthode updateTextView( ).

  • Ligne 2 : Nous créons une variable statique privée contenant un identifiant destiné à notre Bundle, plus facile à manipuler.

  • Ligne 3 : On modifie notre méthode updateTextView( ) afin d'enregistrer temporairement la valeur du TAG d'un bouton directement dans la variable de notre classe, buttonTag.

  • Ligne 4 : C'est à ce moment que nous enregistrons la valeur de buttonTag dans notre Bundle, juste avant que notre fragment soit détruit.

  • Ligne 5 : Nous restaurons ici le TAG du bouton précédemment enregistré dans notre Bundle.

  • Ligne 6 : Enfin, nous récupérons la valeur de buttonTag précédemment enregistrée et mettons à jour notre TextView.

Voilà ! Nous savons dorénavant gérer correctement la sauvegarde de nos informations dans un contexte comme celui-là, notamment lorsque les fragments sont détruits lors d'une rotation.

Conclusion

Félicitations ! Ce chapitre vous aura permis d'optimiser la stabilité et les performances de notre application à base de fragments, sur smartphone et sur tablette.

Encore une fois, n'hésitez pas à modifier cette application, à jouer un peu avec en la faisant évoluer avec de nouveaux écrans, de nouvelles activités, etc... Votre progression n'en sera que meilleure !

Vous trouverez le code final de l'application MyFragmentApp à cette adresse.

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