I Fragments, come abbiamo già ricordato, sono una caratteristica potente di Android per la creazione di interfacce utente dinamiche e riutilizzabili ma spesso nascondono delle insidie. Buona parte di queste trappole si celano nel ciclo di vita, articolato ma non difficile da padroneggiare se approcciato con la necessaria cura: di questo tratta il nostro post.
Le fasi del ciclo di vita
Il diagramma che segue mostra le fasi del ciclo di vita di un Fragment messe a confronto con quelle dell’Activity:
Quello che è mostrato è tutta l’esistenza di un Fragment da quando è collegato all’interfaccia a quando viene (eventualmente) distrutto.Al centro (tra i metodi onResume e onPause) si inserisce la fase running, l’interazione con l’utente.
Un fragment può esistere solo all’interno di una Activity per questo il ciclo di vita dell’uno e dell’altra si innestano.
Un’Activity, da quando viene creata a quando diventa running, passa per tre metodi di callback (onCreate, onStart, onResume) e, se contiene fragments, la creazione della sua interfaccia utente consiste proprio nell’istanziazione di questi. I cicli di vita andranno di pari passo: parallelamente, anche il fragment invocherà onCreate, onStart, onResume. La differenza rispetto all’Activity è che il fragment invoca altri suoi metodi specifici:
- onAttach: segna il momento in cui il fragment è collegato all’Activity, questa viene passata al metodo come Context. E’ il punto ideale per salvare un riferimento al Context valido da usare durante la vita del Fragment;
- onCreateView: viene qui prodotto il layout dell’interfaccia che verrà restituito. Tra i parametri in ingresso riceve un LayoutInflater che servirà proprio all’elaborazione di un layout XML;
- onActivityCreated annuncia che la creazione dell’Activity è completa, da questo momento in poi il Fragment potrà interagire con essa.
Quando un’Activity smette di interagire con l’utente verrà invocato onPause, quando non è più visibile onStop fino alla sua (eventuale) distruzione che verrà segnalata da onDestroy. I metodi omonimi dei fragment hanno gli stessi ruoli mentre i due specifici onDestroyView e onDetach segnalano, rispettivamente, la rimozione dell’interfaccia utente e lo scollegamento dall’Activity.
L’esempio
L’esempio che presentiamo ha la finalità principale di stimolare la riflessione sul ciclo di vita dei Fragments. Abbiamo un’Activity contenente due Fragments.
Il primo Fragment ha nell’interfaccia un pulsante che serve ad aprire il secondo:
Il secondo Fragment mostra un contatore numerico ed un pulsante che serve per tornare al primo.
Ogni volta che viene aperto il secondo Fragment il contatore viene incrementato di uno: su questo verte buona parte dell’esempio in quanto solo il corretto riutilizzo dei Fragment permetterà al contatore di essere incrementato e di non ri-inizializzarsi ogni volta mostrando sempre il numero 1.
Il codice
I Fragment si alterneranno in un FrameLayout che costituisce il layout dell’Activity:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Nel metodo onCreate dell’Activity viene caricato il layout e con la FragmentTransaction viene aggiunto il primo Fragment:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.activity_main, new MainFragment(), MainFragment.TAG)
.commit();
}
Il codice del primo Fragment è molto semplice, crea solo l’interfaccia in onCreateView. Se non sono richieste funzionalità particolari, ciò può bastare anche in Fragment più complessi:
public class MainFragment extends Fragment {
public final static String TAG="LC_PRIMO_FRAGMENT";
public MainFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
}
Il layout del primo Fragment contiene un solo pulsante:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Passa al secondo Fragment"
android:onClick="passaAlSecondo"/>
</RelativeLayout>
il suo click invocherà il metodo passaAlSecondo dell’Activity:
public void passaAlSecondo(View v)
{
SecondFragment sf= (SecondFragment) getSupportFragmentManager().findFragmentByTag(SecondFragment.TAG);
if (sf==null)
sf=new SecondFragment();
getSupportFragmentManager()
.beginTransaction()
.addToBackStack(null)
.replace(R.id.activity_main, sf, SecondFragment.TAG)
.commit();
}
Piuttosto che crearlo da zero, prima verifichiamo se un’istanza del secondo fragment è conservata nel FragmentManager. Lo possiamo riconoscere dal TAG associatogli durante il replace della FragmentTransaction.
ll SecondFragment ha un layout che non stupisce (file /res/layout/fragment_second.xml):
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_centerInParent="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:layout_gravity="center_horizontal"
android:textStyle="bold"
android:textSize="25sp"
android:id="@+id/conteggio"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:onClick="tornaAlPrimo"
android:text="Torna al primo Fragment" />
</LinearLayout>
</RelativeLayout>
e nella classe Java che lo rappresenta definiamo il layout in onCreateView mentre in onResume incrementiamo la variabile che tiene il conto delle visualizzazioni e ne inserisce il valore nella TextView.
public class SecondFragment extends Fragment {
public final static String TAG="LC_SECONDO_FRAGMENT";
private Integer conta=0;
public SecondFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_second, container, false);
}
@Override
public void onResume() {
super.onResume();
TextView txt= (TextView) getView().findViewById(R.id.conteggio);
conta++;
txt.setText(conta.toString());
}
}
Senza riciclare il Fragment, il conteggio ripartirebbe sempre da zero, non mostrando mai incrementi.
Cliccando il pulsante presente nel secondo fragment viene invocato il metodo tornaAlPrimo dell’Activity:
public void tornaAlPrimo(View v)
{
MainFragment sf= (MainFragment) getSupportFragmentManager().findFragmentByTag(MainFragment.TAG);
if (sf==null)
sf=new MainFragment();
getSupportFragmentManager()
.beginTransaction()
.addToBackStack(null)
.replace(R.id.activity_main, sf, MainFragment.TAG)
.commit();
}
Anche in questo caso tentiamo di riciclare il Fragment già usato (anche se non si devono conservare dati è comunque una buona prassi) e poi eseguiamo il replace nella FragmentTransaction. Notare che visto che la transazione con cui viene caricato il secondo fragment è caricato nel BackStack sarebbe stato sufficiente in questo metodo chiamare popBackStack che inverte l’ultima transazione eseguita. Ciò però comporterebbe che il Fragment dismesso (il secondo in questo caso) verrebbe del tutto distrutto fino alla chiamata del metodo onDetach: la conseguenza sarebbe una creazione da zero alla riapertura del secondo fragment con il conteggio che ricomincerebbe.
Ricapitolando…
In questo articolo, abbiamo visto il ciclo di vita dei Fragments ma non ci siamo limitati a elencare le fasi che lo compongono. L’esempio che abbiamo trattato utilizza l’espediente del conteggio allo scopo di invitare il programmatore al riciclo dei Fragments. Come è stato svolto – utilizzando sempre replace nonchè l’impiego dei tag per il recupero di Fragment già creati ma evitando l’uso di popBackStack – il ciclo di vita nella fase che segue il running non va mai oltre il metodo onDestroyView, smantellando quindi l’interfaccia utente ma senza distruggere il componente. Conseguenza di ciò, al momento di ripristinare un fragment già visitato non ripartiamo da onAttach bensì da onCreateView ed il nostro conteggio sarà salvo.
I Fragment vanno usati con accortezza, solo in questo modo potremo sprigionare le loro potenzialità senza incappare in errori: la tematica è interessante e piena di implicazioni, non finiremo certo qui di trattarla.
Alla prossima!













No Responses to “Android Fragments: sperimentiamo il ciclo di vita”