Fragments in Android: Guida Completa con Java
I Fragment sono uno dei concetti fondamentali nello sviluppo Android moderno. In questa guida completa vedremo cosa sono, quando usarli, come funzionano al loro interno e come integrarli correttamente nelle tue Activity utilizzando Java. La guida è pensata per sviluppatori Android principianti e intermedi che vogliono consolidare le basi e scrivere codice pulito e manutenibile.
Cosa sono i Fragment in Android
Un Fragment è una porzione riutilizzabile di interfaccia utente e logica che vive all’interno di una Activity. Puoi pensarlo come un “mini-controller” con il proprio layout, il proprio ciclo di vita e i propri metodi di gestione degli eventi, ma che dipende dall’Activity che lo ospita.
Caratteristiche principali di un Fragment:
- Ha un proprio layout XML separato.
- Ha un ciclo di vita differente ma collegato a quello dell’Activity.
- Può essere aggiunto, rimosso e sostituito a runtime.
- Può essere riutilizzato in diverse Activity o configurazioni (telefono, tablet).
- Supporta una gestione dedicata dello back stack per la navigazione interna.
Quando usare i Fragment
Non è obbligatorio usare i Fragment in ogni progetto, ma diventano molto utili in vari scenari:
- Interfacce multi-pannello: ad esempio layout diversi per smartphone e tablet.
- Riutilizzo di componenti UI: la stessa schermata di dettaglio può essere usata in più Activity.
- Navigazione interna complessa: schermate che cambiano nella stessa Activity mantenendo elementi comuni (toolbar, bottom navigation).
- Modularità del codice: separare responsabilità e logica per mantenere il codice più pulito.
Se la tua app ha una singola schermata molto semplice, una sola Activity potrebbe bastare. Quando però inizi ad avere più schermate collegate tra loro o layout che devono adattarsi a dispositivi diversi, i Fragment diventano la soluzione naturale.
Struttura di base di una classe Fragment in Java
Un Fragment in Java si ottiene estendendo la classe Fragment. Qui sotto un esempio base di un ExampleFragment con i metodi più comuni:
public class ExampleFragment extends Fragment {
public ExampleFragment() {
// Costruttore vuoto richiesto
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inizializzazione logica non legata alla UI
if (getArguments() != null) {
// Recupero parametri passati con setArguments()
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Infla il layout XML del Fragment
View view = inflater.inflate(R.layout.fragment_example, container, false);
// Inizializza qui le view (findViewById sulla view del fragment)
// Button button = view.findViewById(R.id.my_button);
return view;
}
}
I due metodi più importanti che vedremo nel dettaglio sono onCreate() e onCreateView(), strettamente legati al ciclo di vita del Fragment.
Il ciclo di vita di un Fragment
Il ciclo di vita di un Fragment è più complesso di quello di una Activity perché deve tenere conto sia della propria esistenza sia di quella dell’Activity che lo ospita. Ecco i metodi principali, in ordine semplificato:
onAttach(): il Fragment viene collegato all’Activity.onCreate(): inizializzazione logica, senza accesso alla UI.onCreateView(): creazione della UI (inflating del layout XML).onViewCreated(): la view è creata, qui puoi inizializzare componenti UI complessi o listener.onStart(): il Fragment sta diventando visibile.onResume(): il Fragment è visibile e l’utente può interagire.onPause(): il Fragment sta per andare in pausa (es. nuova schermata sopra).onStop(): il Fragment non è più visibile.onDestroyView(): la view viene distrutta (rilascia riferimenti alla UI per evitare memory leak).onDestroy(): il Fragment viene distrutto.onDetach(): il Fragment viene scollegato dall’Activity.
È importante ricordare che il Fragment può restare in memoria anche quando la sua view è stata distrutta: per questo motivo è buona pratica liberare tutte le risorse legate alle view in onDestroyView().
Creare il layout XML di un Fragment
Il layout di un Fragment è un normale layout XML che verrà inflato in onCreateView(). Supponiamo di voler creare un Fragment che mostri un titolo e un pulsante. Creiamo fragment_example.xml in res/layout:
<?xml version="1.0" encoding="utf-8"?>
<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"
android:padding="16dp">
<TextView
android:id="@+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ciao dal Fragment!"
android:textSize="18sp"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/button_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fai qualcosa" />
</LinearLayout>
Nel Fragment Java, infliamo questo layout e colleghiamo le view:
public class ExampleFragment extends Fragment {
private TextView textTitle;
private Button buttonAction;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_example, container, false);
textTitle = view.findViewById(R.id.text_title);
buttonAction = view.findViewById(R.id.button_action);
buttonAction.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), "Button cliccato nel Fragment", Toast.LENGTH_SHORT).show();
}
});
return view;
}
}
Aggiungere un Fragment a una Activity con FragmentManager e FragmentTransaction
Per mostrare un Fragment, l’Activity deve fornire un contenitore, solitamente un FrameLayout nel layout dell’Activity. Ecco un esempio di activity_main.xml con un contenitore per i Fragment:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Nell’Activity Java (ad esempio MainActivity), aggiungiamo il Fragment usando FragmentManager e FragmentTransaction:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
// Aggiungiamo il Fragment solo la prima volta
ExampleFragment fragment = new ExampleFragment();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, fragment, "EXAMPLE_FRAGMENT")
.commit();
}
}
}
Il metodo add() aggiunge il Fragment al contenitore specificato. Il terzo parametro è un tag opzionale utile per recuperare il Fragment in seguito.
Sostituire un Fragment esistente
Spesso avrai bisogno di sostituire un Fragment con un altro (ad esempio passare da una lista a un dettaglio). In questo caso, invece di add() usiamo replace():
public void showDetailFragment() {
DetailFragment detailFragment = new DetailFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, detailFragment, "DETAIL_FRAGMENT")
.addToBackStack(null) // Aggiungiamo la transazione allo stack
.commit();
}
Con replace(), il Fragment attuale nel contenitore viene rimosso e sostituito con quello nuovo. Il metodo addToBackStack() consente all’utente di tornare indietro al Fragment precedente premendo il tasto “back”.
Passare dati tra Activity e Fragment con Bundle
Passare dati dall’Activity al Fragment
Il modo raccomandato per passare dati a un Fragment è utilizzare un Bundle e il metodo setArguments(). Esempio con un metodo newInstance():
public class DetailFragment extends Fragment {
private static final String ARG_ITEM_ID = "item_id";
private String itemId;
public static DetailFragment newInstance(String itemId) {
DetailFragment fragment = new DetailFragment();
Bundle args = new Bundle();
args.putString(ARG_ITEM_ID, itemId);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
itemId = getArguments().getString(ARG_ITEM_ID);
}
}
}
Nell’Activity, creiamo il Fragment passando il parametro:
public void openDetail(String itemId) {
DetailFragment fragment = DetailFragment.newInstance(itemId);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit();
}
Passare dati dal Fragment all’Activity
Per comunicare dal Fragment all’Activity, puoi usare diversi approcci. Uno dei più semplici (in Java) è definire un’interfaccia nel Fragment e farla implementare all’Activity:
public class ExampleFragment extends Fragment {
public interface OnFragmentInteractionListener {
void onButtonClicked(String message);
}
private OnFragmentInteractionListener listener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
listener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_example, container, false);
Button button = view.findViewById(R.id.button_action);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onButtonClicked("Clic dal Fragment!");
}
}
});
return view;
}
@Override
public void onDetach() {
super.onDetach();
listener = null;
}
}
L’Activity implementerà l’interfaccia:
public class MainActivity extends AppCompatActivity
implements ExampleFragment.OnFragmentInteractionListener {
@Override
public void onButtonClicked(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
Gestire il back stack dei Fragment
Il back stack dei Fragment è una pila di transazioni che consente di gestire la navigazione interna alla stessa Activity. Quando chiami addToBackStack() su una transazione, stai dicendo al sistema che quella sostituzione di Fragment deve essere annullata quando l’utente preme il tasto “back”.
Esempio con due Fragment (lista e dettaglio):
public void openDetailFragment() {
DetailFragment detailFragment = new DetailFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, detailFragment)
.addToBackStack("detail")
.commit();
}
Per gestire manualmente il back stack puoi usare metodi come:
getSupportFragmentManager().popBackStack(): torna al Fragment precedente.getSupportFragmentManager().popBackStackImmediate(): stessa cosa ma in modo sincrono.getSupportFragmentManager().getBackStackEntryCount(): numero di transazioni nello stack.
Spesso è utile controllare il back stack nel metodo onBackPressed() dell’Activity (o l’equivalente con il nuovo OnBackPressedDispatcher) per decidere se chiudere l’Activity o solo tornare al Fragment precedente.
@Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
} else {
super.onBackPressed();
}
}
Errori comuni con i Fragment (e come evitarli)
- Usare il costruttore con parametri: evita di creare Fragment con costruttori personalizzati. Usa sempre un costruttore vuoto e metodi
newInstance()conBundle. - Accedere alle view fuori da onCreateView/onViewCreated: le view possono essere nulle se il Fragment è stato distrutto. Inizializza e usa le view solo dopo la creazione della UI e liberale in
onDestroyView(). - Non usare addToBackStack() quando necessario: se dimentichi di aggiungere la transazione allo stack, il tasto “back” chiuderà l’Activity invece di tornare al Fragment precedente.
- Mischiare FragmentManager diversi: in un’Activity compat usa sempre
getSupportFragmentManager()(dalla libreria AndroidX), non ilFragmentManager“classico”. - Memory leak: non mantenere riferimenti statici a Activity o Fragment, evita inner class non statiche non necessarie e rimuovi i listener nella fase corretta del ciclo di vita.
Esempio pratico completo: Lista > Dettaglio con Fragment
Vediamo ora un mini esempio completo con due Fragment: ListFragment e DetailFragment, gestiti da una MainActivity.
Layout Activity: contenitore per i Fragment
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
ListFragment: mostra una lista semplice
public class ListFragment extends Fragment {
public interface OnItemSelectedListener {
void onItemSelected(String itemId);
}
private OnItemSelectedListener listener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnItemSelectedListener) {
listener = (OnItemSelectedListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnItemSelectedListener");
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_list, container, false);
ListView listView = view.findViewById(R.id.list_view);
final String[] items = {"Uno", "Due", "Tre"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(
getActivity(),
android.R.layout.simple_list_item_1,
items
);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (listener != null) {
listener.onItemSelected(items[position]);
}
}
});
return view;
}
@Override
public void onDetach() {
super.onDetach();
listener = null;
}
}
Layout XML per fragment_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
DetailFragment: mostra il dettaglio dell’elemento scelto
public class DetailFragment extends Fragment {
private static final String ARG_ITEM_ID = "item_id";
private String itemId;
public static DetailFragment newInstance(String itemId) {
DetailFragment fragment = new DetailFragment();
Bundle args = new Bundle();
args.putString(ARG_ITEM_ID, itemId);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
itemId = getArguments().getString(ARG_ITEM_ID);
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_detail, container, false);
TextView textView = view.findViewById(R.id.text_detail);
textView.setText("Hai selezionato: " + itemId);
return view;
}
}
Layout XML per fragment_detail.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/text_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp" />
</FrameLayout>
MainActivity: gestione dei due Fragment e del back stack
public class MainActivity extends AppCompatActivity
implements ListFragment.OnItemSelectedListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
// Mostriamo la lista al primo avvio
ListFragment listFragment = new ListFragment();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, listFragment, "LIST")
.commit();
}
}
@Override
public void onItemSelected(String itemId) {
// Quando l'utente seleziona un elemento, apriamo il dettaglio
DetailFragment detailFragment = DetailFragment.newInstance(itemId);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, detailFragment, "DETAIL")
.addToBackStack(null)
.commit();
}
@Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
} else {
super.onBackPressed();
}
}
}
Conclusione
I Fragment sono uno strumento potente per creare interfacce modulari, riutilizzabili e flessibili nelle app Android. In questa guida abbiamo visto cosa sono, quando usarli, la loro struttura, il ciclo di vita, come definirne il layout XML, come aggiungerli e sostituirli tramite FragmentManager e FragmentTransaction, come passare dati tra Activity e Fragment con Bundle, come gestire il back stack e quali sono gli errori più comuni da evitare.
Da qui puoi approfondire l’uso dei Fragment con la Navigation Component di Android Jetpack, l’uso delle ViewModel condivise tra Activity e Fragment e la gestione avanzata del ciclo di vita. Intanto, padroneggiare le basi in Java come mostrato in questa guida ti darà una solida base per costruire app Android più strutturate e professionali.
Lascia un commento