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 ?
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 :
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"
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"
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"
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"
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.