Fragment Lifecycle — ściśle określona sekwencja metod callback, które Android wywołuje w ciągu życia Fragment: od utworzenia (onAttach) do całkowitego usunięcia (onDetach). Fragment ma bardziej złożony cykl życia niż Activity — obejmuje 11 stanów i 7 podstawowych callbacków. Fragment Lifecycle jest zarządzany przez FragmentManager i ściśle powiązany z cyklem życia zawierającego go Activity. Według danych Google, Fragment jest używany w 74% aplikacji Android działających na API Level 21+, co czyni zrozumienie Fragment Lifecycle obowiązkowym dla profesjonalnego programowania Android. Dokumentacja Android na temat Fragment Lifecycle opisuje wszystkie stany i gwarancje wywołania.
Najważniejsze
Fragment Lifecycle to zestaw powiązanych stanów i metod, przez które przechodzi każda instancja Fragment od momentu utworzenia do zniszczenia. W przeciwieństwie do Activity, cykl życia Fragment jest powiązany z dwoma kontekstami: sam Fragment (żyje od onAttach do onDetach) i jego View (żyje od onCreateView do onDestroyView). To rozdzielenie jest kluczową cechą Fragment, pozwalającą przetrwać zniszczenie View przy obrocie ekranu bez niszczenia samego Fragment.
Pełna sekwencja callbacków Fragment:
Według Google, średni fragment w nowoczesnej aplikacji przechodzi pełny cykl 3–5 razy na sesję użytkownika (z powodu obrotów ekranu i nawigacji). Prawidłowe obsłużenie wszystkich faz jest podstawą stabilności UI.
FragmentManager zarządza Fragment przez pięć głównych stanów, zdefiniowanych w klasie Fragment.State. Każdy stan odpowiada określonemu zestawowi callbacków, które zostały wykonane.
| Stan | Znaczenie | Wykonane callbacki |
|---|---|---|
| INITIALIZED | Fragment utworzony, ale View jeszcze nie ma | onAttach, onCreate |
| CREATED | View utworzony, ale Fragment nie jest widoczny | + onCreateView, onViewCreated |
| STARTED | Fragment widoczny, ale nieaktywny | + onStart |
| RESUMED | Fragment aktywny, współdziała z użytkownikiem | + onResume |
| DESTROYED | Fragment zniszczony | + onDestroyView, onDestroy, onDetach |
FragmentManager przenosi Fragment między stanami w zależności od działań użytkownika i zdarzeń systemowych. Przy dodawaniu Fragment do kontenera przechodzi kolejno INITIALIZED → CREATED → STARTED → RESUMED. Przy usuwaniu — RESUMED → STARTED → CREATED → DESTROYED.
Stan CREATED — szczególny: View może być zniszczony (po onDestroyView), ale sam Fragment pozostaje w stanie CREATED (po onDestroyView, przed onDestroy). Pozwala to FragmentManager zachować Fragment w pamięci bez View, co jest niezbędne do przetrwania obrotów ekranu.
Fragment Lifecycle i Activity Lifecycle są ściśle powiązane, ale mają fundamentalne różnice. Fragment zawsze żyje wewnątrz Activity, a jego cykl życia zależy od Activity-gospodarza, ale nie jest mu identyczny.
| Aspekt | Activity | Fragment |
|---|---|---|
| Liczba callbacków | 7 (onCreate … onDestroy) | 11 (onAttach … onDetach) |
| Oddzielny Lifecycle dla View | Nie | Tak (viewLifecycleOwner) |
| Przetrwa obrót | Nie (niszczony) | Tak (ViewModel + Fragment przetrwają) |
| Zależność od hosta | Nie | Zależy od Activity Lifecycle |
| Zapisywanie stanu | onSaveInstanceState | onSaveInstanceState (fragmentowe) |
| Zarządzanie | System | FragmentManager |
Główna praktyczna różnica: przy obrocie ekranu Activity jest niszczone całkowicie (onDestroy) i tworzone na nowo (onCreate). Fragment przy obrocie przechodzi przez onDestroyView (View jest niszczony) → onCreateView (View jest tworzony na nowo), ale sam Fragment i jego ViewModel pozostają żywe. To czyni Fragment idealnym kontenerem dla logiki UI, która powinna przetrwać zmiany konfiguracyjne.
Kolejność wywołań przy obrocie ekranu: Activity.onPause → Fragment.onPause → Activity.onStop → Fragment.onStop → Activity.onDestroy → Fragment.onDestroyView → (Activity zniszczona) → Activity.onCreate → Fragment.onAttach → Fragment.onCreate → Fragment.onCreateView → Fragment.onViewCreated → Activity.onStart → Fragment.onStart → Activity.onResume → Fragment.onResume.
FragmentManager — centralna klasa odpowiedzialna za dodawanie, usuwanie, zastępowanie fragmentów i zarządzanie ich stanami. FragmentManager prowadzi stos BackStack i gwarantuje prawidłową kolejność callbacków przy transakcjach. Każde Activity i każdy zagnieżdżony Fragment mają swój FragmentManager.
Główne operacje FragmentManager:
BackStack — stos transakcji FragmentManager. Po naciśnięciu systemowego przycisku „Wstecz" ostatnia transakcja w BackStack jest cofana (popBackStack()). Fragment usunięty przez popBackStack jest przywracany. Jeśli BackStack jest pusty, naciśnięcie „Wstecz" kończy Activity.
Według Google, 78% problemów z Fragment (duplikowanie, puste ekrany, IllegalStateException) jest związanych z nieprawidłowym użyciem FragmentManager. Główna zasada: wykonuj transakcje przez commit() (asynchronicznie) lub commitNow() (synchronicznie) w zależności od kontekstu. commit() gwarantuje prawidłową kolejność przy wielokrotnych transakcjach.
Fragment obsługuje własny mechanizm zapisywania stanu przez onSaveInstanceState, który działa niezależnie od Activity. Fragment zapisuje stan w Bundle, który jest przekazywany do onCreate i onCreateView przy przywracaniu.
Kiedy Fragment zapisuje stan:
Nowoczesne podejście: używaj SavedStateHandle w ViewModel do zapisywania stanu Fragment. SavedStateHandle automatycznie zapisuje i przywraca dane przy obrocie ekranu i process death, nie wymagając ręcznego onSaveInstanceState. Google zaleca SavedStateHandle jako preferowany sposób zapisywania stanu UI w Fragment.
setRetainInstance (przestarzałe od Fragment 1.3): wcześniej Fragment mógł być zachowywany przez setRetainInstance(true) przy obrocie ekranu. To podejście zostało zastąpione przez ViewModel + SavedStateHandle, które działają niezawodniej i nie wymagają specjalnej konfiguracji.
viewLifecycleOwner — Lifecycle powiązany z View Fragment (od onCreateView do onDestroyView). To koncepcyjnie ważne: subskrypcje LiveData/Flow wykonane przez viewLifecycleOwner są automatycznie anulowane przy zniszczeniu View (onDestroyView), ale nie wpływają na sam Fragment.
Różnica między viewLifecycleOwner a lifecycle Fragment:
Dlaczego to ważne: jeśli subskrybujesz LiveData przez lifecycle Fragment (this), to po onDestroyView subskrypcja pozostaje aktywna i LiveData będzie próbować aktualizować null View, powodując NPE. Subskrypcja przez viewLifecycleOwner gwarantuje, że po onDestroyView żadne aktualizacje UI nie nastąpią.
Zasada: w Fragment zawsze używaj viewLifecycleOwner do subskrypcji LiveData, Flow i korutyn związanych z UI. Do korutyn ViewModel używaj viewModelScope — jest on powiązany z ViewModel, a nie z Fragment.
Demonstruje prawidłową inicjalizację UI i subskrypcję LiveData przez 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", "Aktualizacja listy: ${users.size} użytkowników")
}
}
override fun onDestroyView() {
super.onDestroyView()
Log.d("UserListFragment", "onDestroyView: View zniszczony")
}
}
Fragment nadmuchuje layout w onCreateView, konfiguruje UI i subskrybuje LiveData w onViewCreated. Subskrypcja przez viewLifecycleOwner — obowiązkowy wymóg zapobiegania wyciekom. onDestroyView loguje zniszczenie View — potwierdzenie, że Fragment przetrwa obrót ekranu.
Demonstruje dodawanie Fragment przez FragmentManager w Activity, zastępowanie z BackStack i przywracanie.
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", "Ładowanie szczegółów użytkownika: $userId")
}
}
Activity używa supportFragmentManager do zarządzania fragmentami. Transakcja add() z BackStack gwarantuje, że po naciśnięciu „Wstecz" HomeFragment zostanie przywrócony. openDetail() zastępuje bieżący Fragment na DetailFragment z argumentami. Sprawdzenie savedInstanceState == null zapobiega duplikowaniu fragmentów przy obrocie ekranu.
Użycie Flow i StateFlow w Fragment z viewLifecycleOwner do reaktywnego aktualizowania 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", "Wyniki wyszukiwania: ${results.size}")
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
Fragment używa View Binding do dostępu do View. Korutyna viewLifecycleOwner.lifecycleScope.launch jest automatycznie anulowana przy zniszczeniu View. Binding jest zerowany w onDestroyView w celu zapobiegania wyciekom. StateFlow gwarantuje aktualność danych przy odtworzeniu View.
Często zadawane pytania
onCreateView — tworzy i zwraca główny View Fragment. onViewCreated — wywoływany zaraz po utworzeniu View, gwarantuje, że View jest w pełni zainicjalizowany i gotowy do konfiguracji (findViewById, subskrypcje). Google zaleca w onCreateView tylko nadmuchiwać layout, a całą konfigurację UI robić w onViewCreated.
onDestroy — Fragment jest zniszczony jako obiekt (ViewModel jest czyszczony, korutyny anulowane). onDetach — ostatni callback, po którym Fragment odłącza się od Activity. Praktycznie wszystkie zasoby powinny być zwolnione w onDestroyView (View) i onDestroy (Fragment). onDetach — do czyszczenia referencji do Activity.
Fragment znika, jeśli nie został dodany do FragmentManager przez transakcję z zachowaniem w BackStack lub jeśli Activity nie przywraca FragmentManager w onCreate. Rozwiązanie: dodawaj Fragment programowo przez supportFragmentManager.beginTransaction().add() w onCreate z sprawdzeniem savedInstanceState == null.
Nie. Fragment jest zawsze powiązany z Activity przez FragmentManager. Nawet przy obrocie ekranu Activity jest odtwarzane, a Fragment ponownie przywiązywany do nowego Activity. Utworzenie Fragment poza Activity jest niemożliwe — konstruktor Fragment wymaga pustego konstruktora do przywrócenia przez system.
Nested fragments (zagnieżdżone fragmenty) — Fragment wewnątrz innego Fragment. Używane do budowania złożonych ekranów: panele z zakładkami, master-detail. Zagnieżdżone fragmenty są zarządzane przez dziecięcy FragmentManager (childFragmentManager). Google zaleca nie przekraczać 2 poziomów zagnieżdżenia, aby uniknąć problemów z wydajnością.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również