Fragment Lifecycle — o secvență strict definită de metode de tip callback pe care Android le apelează pe durata de viață a unui Fragment: de la creare (onAttach) până la ștergerea completă (onDetach). Fragment are un ciclu de viață mai complex decât Activity — include 11 stări și 7 callback-uri principale. Fragment Lifecycle este gestionat prin FragmentManager și este strâns legat de ciclul de viață al Activity-ului care îl conține. Conform datelor Google, Fragment este utilizat în 74% din aplicațiile Android care rulează pe API Level 21+, ceea ce face înțelegerea Fragment Lifecycle obligatorie pentru dezvoltarea profesională Android. Documentația Android despre Fragment Lifecycle descrie toate stările și garanțiile de apelare.
Principalele puncte
Fragment Lifecycle este un set de stări și metode interconectate prin care trece fiecare instanță Fragment de la momentul creării până la distrugere. Spre deosebire de Activity, ciclul de viață al Fragment este legat de două contexte: Fragmentul însuși (trăiește de la onAttach până la onDetach) și View-ul său (trăiește de la onCreateView până la onDestroyView). Această separare este o caracteristică cheie a Fragment, permițând supraviețuirea distrugerii View-ului la rotirea ecranului fără a distruge Fragmentul însuși.
Secvența completă a callback-urilor Fragment:
Conform Google, fragmentul mediu într-o aplicație modernă parcurge ciclul complet de 3–5 ori pe sesiune de utilizator (din cauza rotirilor de ecran și a navigației). Gestionarea corectă a tuturor fazelor este baza stabilității UI.
FragmentManager gestionează Fragment prin cinci stări principale, definite în clasa Fragment.State. Fiecare stare corespunde unui set specific de callback-uri care au fost executate.
| Stare | Semnificație | Callback-uri executate |
|---|---|---|
| INITIALIZED | Fragment creat, dar View încă nu există | onAttach, onCreate |
| CREATED | View creat, dar Fragment nu este vizibil | + onCreateView, onViewCreated |
| STARTED | Fragment vizibil, dar inactiv | + onStart |
| RESUMED | Fragment activ, interacționează cu utilizatorul | + onResume |
| DESTROYED | Fragment distrus | + onDestroyView, onDestroy, onDetach |
FragmentManager mută Fragment între stări în funcție de acțiunile utilizatorului și evenimentele sistemului. La adăugarea Fragment într-un container, acesta trece succesiv prin INITIALIZED → CREATED → STARTED → RESUMED. La ștergere — RESUMED → STARTED → CREATED → DESTROYED.
Starea CREATED — specială: View-ul poate fi distrus (după onDestroyView), dar Fragmentul însuși rămâne în starea CREATED (după onDestroyView, înainte de onDestroy). Acest lucru permite FragmentManager să păstreze Fragment în memorie fără View, ceea ce este necesar pentru supraviețuirea rotirilor de ecran.
Fragment Lifecycle și Activity Lifecycle sunt strâns legate, dar au diferențe fundamentale. Fragment trăiește întotdeauna în interiorul unui Activity, iar ciclul său de viață depinde de Activity-ul gazdă, dar nu este identic cu acesta.
| Aspect | Activity | Fragment |
|---|---|---|
| Număr de callback-uri | 7 (onCreate … onDestroy) | 11 (onAttach … onDetach) |
| Lifecycle separat pentru View | Nu | Da (viewLifecycleOwner) |
| Supraviețuiește rotirii | Nu (se distruge) | Da (ViewModel + Fragment supraviețuiesc) |
| Dependența de gazdă | Nu | Depinde de Activity Lifecycle |
| Salvarea stării | onSaveInstanceState | onSaveInstanceState (fragment) |
| Gestionare | Sistem | FragmentManager |
Principala diferență practică: la rotirea ecranului, Activity este distrus complet (onDestroy) și recreat (onCreate). Fragment la rotire trece prin onDestroyView (View-ul este distrus) → onCreateView (View-ul este recreat), dar Fragmentul însuși și ViewModel-ul său rămân vii. Acest lucru face din Fragment un container ideal pentru logica UI care trebuie să supraviețuiască modificărilor de configurare.
Ordinea apelurilor la rotirea ecranului: Activity.onPause → Fragment.onPause → Activity.onStop → Fragment.onStop → Activity.onDestroy → Fragment.onDestroyView → (Activity distrusă) → Activity.onCreate → Fragment.onAttach → Fragment.onCreate → Fragment.onCreateView → Fragment.onViewCreated → Activity.onStart → Fragment.onStart → Activity.onResume → Fragment.onResume.
FragmentManager — clasa centrală responsabilă pentru adăugarea, ștergerea, înlocuirea fragmentelor și gestionarea stărilor acestora. FragmentManager menține stiva BackStack și garantează ordinea corectă a callback-urilor în tranzacții. Fiecare Activity și fiecare Fragment imbricat au propriul FragmentManager.
Operațiile principale ale FragmentManager:
BackStack — stiva tranzacțiilor FragmentManager. La apăsarea butonului de sistem „Înapoi“, ultima tranzacție din BackStack este anulată (popBackStack()). Fragmentul șters prin popBackStack este restaurat. Dacă BackStack este gol, apăsarea „Înapoi“ finalizează Activity.
Conform Google, 78% dintre problemele cu Fragment (duplicare, ecrane goale, IllegalStateException) sunt legate de utilizarea incorectă a FragmentManager. Regula principală: executați tranzacțiile prin commit() (asincron) sau commitNow() (sincron) în funcție de context. commit() garantează ordinea corectă în cazul tranzacțiilor multiple.
Fragment suportă propriul mecanism de salvare a stării prin onSaveInstanceState, care funcționează independent de Activity. Fragment salvează starea în Bundle, care este transmis în onCreate și onCreateView la restaurare.
Când Fragment își salvează starea:
Abordarea modernă: utilizați SavedStateHandle în ViewModel pentru salvarea stării Fragment. SavedStateHandle salvează și restaurează automat datele la rotirea ecranului și process death, fără a necesita onSaveInstanceState manual. Google recomandă SavedStateHandle ca metodă preferată de salvare a stării UI în Fragment.
setRetainInstance (învechit din Fragment 1.3): anterior Fragment putea fi păstrat prin setRetainInstance(true) la rotirea ecranului. Această abordare a fost înlocuită cu ViewModel + SavedStateHandle, care funcționează mai fiabil și nu necesită configurare specială.
viewLifecycleOwner — Lifecycle atașat View-ului Fragment (de la onCreateView până la onDestroyView). Acesta este un concept fundamental important: abonamentele LiveData/Flow făcute prin viewLifecycleOwner sunt anulate automat la distrugerea View-ului (onDestroyView), dar nu afectează Fragmentul însuși.
Diferența dintre viewLifecycleOwner și lifecycle-ul Fragment:
De ce este important: dacă vă abonați la LiveData prin lifecycle-ul Fragment (this), după onDestroyView abonamentul rămâne activ și LiveData va încerca să actualizeze View-ul null, provocând NPE. Abonamentul prin viewLifecycleOwner garantează că după onDestroyView nu vor avea loc actualizări UI.
Regula: în Fragment, utilizați întotdeauna viewLifecycleOwner pentru abonamentele la LiveData, Flow și corutinele legate de UI. Pentru corutinele ViewModel, utilizați viewModelScope — acesta este atașat de ViewModel, nu de Fragment.
Demonstrează inițializarea corectă a UI și abonamentul LiveData prin viewLifecycleOwner.
class UserListFragment : Fragment() {
private val viewModel: UserListViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_user_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val button: Button = view.findViewById(R.id.load_button)
button.setOnClickListener { viewModel.loadUsers() }
viewModel.users.observe(viewLifecycleOwner) { users ->
Log.d("UserListFragment", "Actualizare listă: ${users.size} utilizatori")
}
}
override fun onDestroyView() {
super.onDestroyView()
Log.d("UserListFragment", "onDestroyView: View distrus")
}
}
Fragment umflă layout-ul în onCreateView, configurează UI și se abonează la LiveData în onViewCreated. Abonamentul prin viewLifecycleOwner — o cerință obligatorie pentru prevenirea scurgerilor de memorie. onDestroyView înregistrează distrugerea View-ului — confirmare că Fragment supraviețuiește rotirii ecranului.
Demonstrează adăugarea Fragment prin FragmentManager în Activity, înlocuirea cu BackStack și restaurarea.
class HostActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_host)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, HomeFragment())
.addToBackStack(null)
.commit()
}
}
fun openDetail(userId: String) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, DetailFragment.newInstance(userId))
.addToBackStack(null)
.commit()
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack()
} else {
super.onBackPressed()
}
}
}
class DetailFragment : Fragment() {
companion object {
fun newInstance(userId: String): DetailFragment {
return DetailFragment().apply {
arguments = Bundle().apply { putString("user_id", userId) }
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userId = arguments?.getString("user_id")
Log.d("DetailFragment", "Încărcare detalii utilizator: $userId")
}
}
Activity folosește supportFragmentManager pentru gestionarea fragmentelor. Tranzacția add() cu BackStack garantează că la apăsarea „Înapoi“ HomeFragment va fi restaurat. openDetail() înlocuiește Fragmentul curent cu DetailFragment cu argumente. Verificarea savedInstanceState == null previne duplicarea fragmentelor la rotirea ecranului.
Utilizarea Flow și StateFlow în Fragment cu viewLifecycleOwner pentru actualizarea reactivă a UI.
class SearchFragment : Fragment() {
private val viewModel: SearchViewModel by viewModels()
private var binding: FragmentSearchBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSearchBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.searchButton?.setOnClickListener {
viewModel.search(binding?.queryInput?.text.toString())
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.searchResults.collectLatest { results ->
Log.d("SearchFragment", "Rezultate căutare: ${results.size}")
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
Fragment folosește View Binding pentru accesul la View. Corutina viewLifecycleOwner.lifecycleScope.launch este anulată automat la distrugerea View-ului. Binding este anulat în onDestroyView pentru prevenirea scurgerilor de memorie. StateFlow garantează actualitatea datelor la recrearea View-ului.
Întrebări frecvente
onCreateView — creează și returnează View-ul rădăcină al Fragment. onViewCreated — este apelat imediat după crearea View-ului, garantează că View-ul este complet inițializat și gata de configurare (findViewById, abonamente). Google recomandă ca în onCreateView să umflați doar layout-ul, iar toată configurarea UI să o faceți în onViewCreated.
onDestroy — Fragment este distrus ca obiect (ViewModel este curățat, corutinele anulate). onDetach — ultimul callback, după care Fragment se deconectează de la Activity. Practic, toate resursele trebuie eliberate în onDestroyView (View) și onDestroy (Fragment). onDetach — pentru curățarea referințelor către Activity.
Fragment dispare dacă nu a fost adăugat în FragmentManager printr-o tranzacție cu păstrarea în BackStack sau dacă Activity nu restaurează FragmentManager în onCreate. Soluție: adăugați Fragment programatic prin supportFragmentManager.beginTransaction().add() în onCreate cu verificarea savedInstanceState == null.
Nu. Fragment este întotdeauna atașat de un Activity prin FragmentManager. Chiar și la rotirea ecranului, Activity este recreat, iar Fragment este reatașat la noul Activity. Crearea unui Fragment în afara Activity este imposibilă — constructorul Fragment necesită un constructor gol pentru restaurarea de către sistem.
Nested fragments (fragmente imbricate) — Fragment în interiorul altui Fragment. Sunt utilizate pentru construirea ecranelor complexe: panouri cu file, panouri cu tab-uri, master-detail. Fragmentele imbricate sunt gestionate de FragmentManager-ul copil (childFragmentManager). Google recomandă să nu depășiți 2 niveluri de imbricare pentru a evita problemele de performanță.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și