Fragment Lifecycle — строго дефинисана секвенца метода-повратних позива које Android позива током живота Fragment-а: од креирања (onAttach) до потпуног уклањања (onDetach). Fragment има сложенији животни циклус од Activity-ја — укључује 11 стања и 7 основних повратних позива. Fragment Lifecycle се управља кроз FragmentManager и уско је повезан са животним циклусом Activity-ја који га садржи. Према Google подацима, Fragment се користи у 74% Android апликација које раде на API Level 21+, што чини разумевање Fragment Lifecycle-а обавезним за професионални Android развој. Android документација о Fragment Lifecycle-у описује сва стања и гаранције позива.
Главно
Fragment Lifecycle је скуп међусобно повезаних стања и метода кроз које пролази свака инстанца Fragment-а од тренутка креирања до уништења. За разлику од Activity-ја, животни циклус Fragment-а је везан за два контекста: сам Fragment (живи од onAttach до onDetach) и његов View (живи од onCreateView до onDestroyView). Ово раздвајање је кључна карактеристика Fragment-а, која омогућава преживљавање уништења View-а при ротацији екрана без уништавања самог Fragment-а.
Потпуна секвенца повратних позива Fragment-а:
Према Google-у, просечан фрагмент у модерној апликацији пролази комплетан циклус 3–5 пута по корисничкој сесији (због ротација екрана и навигације). Исправно обрађивање свих фаза је основа стабилности UI-ја.
FragmentManager управља Fragment-ом кроз пет главних стања, дефинисаних у класи Fragment.State. Свако стање одговара одређеном скупу повратних позива који су извршени.
| Стање | Значење | Извршени повратни позиви |
|---|---|---|
| INITIALIZED | Fragment креиран, али View још не постоји | onAttach, onCreate |
| CREATED | View креиран, али Fragment није видљив | + onCreateView, onViewCreated |
| STARTED | Fragment видљив, али неактиван | + onStart |
| RESUMED | Fragment активан, интерагује са корисником | + onResume |
| DESTROYED | Fragment уништен | + onDestroyView, onDestroy, onDetach |
FragmentManager помера Fragment између стања у зависности од корисничких радњи и системских догађаја. При додавању Fragment-а у контејнер, он секвенцијално пролази INITIALIZED → CREATED → STARTED → RESUMED. При уклањању — RESUMED → STARTED → CREATED → DESTROYED.
Стање CREATED — посебно: View може бити уништен (након onDestroyView), али сам Fragment остаје у стању CREATED (након onDestroyView, пре onDestroy). Ово омогућава FragmentManager-у да задржи Fragment у меморији без View-а, што је неопходно за преживљавање ротација екрана.
Fragment Lifecycle и Activity Lifecycle су уско повезани, али имају фундаменталне разлике. Fragment увек живи унутар Activity-ја, а његов животни циклус зависи од Activity-ја домаћина, али није идентичан њему.
| Аспект | Activity | Fragment |
|---|---|---|
| Број повратних позива | 7 (onCreate … onDestroy) | 11 (onAttach … onDetach) |
| Одвојени Lifecycle за View | Не | Да (viewLifecycleOwner) |
| Преживљава ротацију | Не (уништава се) | Да (ViewModel + Fragment преживљавају) |
| Зависност од домаћина | Не | Зависи од Activity Lifecycle-а |
| Чување стања | onSaveInstanceState | onSaveInstanceState (фрагментно) |
| Управљање | Систем | FragmentManager |
Главна практична разлика: при ротацији екрана, Activity се потпуно уништава (onDestroy) и поново креира (onCreate). Fragment при ротацији пролази кроз onDestroyView (View се уништава) → onCreateView (View се поново креира), али сам Fragment и његов ViewModel остају живи. Ово чини Fragment идеалним контејнером за UI логику која треба да преживи конфигурационе промене.
Редослед позива при ротацији екрана: Activity.onPause → Fragment.onPause → Activity.onStop → Fragment.onStop → Activity.onDestroy → Fragment.onDestroyView → (Activity уништена) → Activity.onCreate → Fragment.onAttach → Fragment.onCreate → Fragment.onCreateView → Fragment.onViewCreated → Activity.onStart → Fragment.onStart → Activity.onResume → Fragment.onResume.
FragmentManager — централна класа одговорна за додавање, уклањање, замену фрагмената и управљање њиховим стањима. FragmentManager води стек BackStack и гарантује исправан редослед повратних позива при трансакцијама. Сваки Activity и сваки угнежђени Fragment имају свој FragmentManager.
Главне операције FragmentManager-а:
BackStack — стек трансакција FragmentManager-а. При притиску на системско дугме „Назад“, последња трансакција у BackStack-у се поништава (popBackStack()). Fragment уклоњен кроз popBackStack се обнавља. Ако је BackStack празан, притисак „Назад“ завршава Activity.
Према Google-у, 78% проблема са Fragment-ом (дуплирање, празни екрани, IllegalStateException) повезано је са неправилним коришћењем FragmentManager-а. Главно правило: извршавајте трансакције кроз commit() (асинхроно) или commitNow() (синхроно) у зависности од контекста. commit() гарантује исправан редослед при вишеструким трансакцијама.
Fragment подржава сопствени механизам чувања стања кроз onSaveInstanceState, који ради независно од Activity-ја. Fragment чува стање у Bundle-у, који се прослеђује у onCreate и onCreateView при обнављању.
Када Fragment чува стање:
Модерни приступ: користите SavedStateHandle у ViewModel-у за чување стања Fragment-а. SavedStateHandle аутоматски чува и обнавља податке при ротацији екрана и process death-у, без потребе за ручним onSaveInstanceState. Google препоручује SavedStateHandle као пожељан начин чувања стања UI-ја у Fragment-у.
setRetainInstance (застарело од Fragment 1.3): раније је Fragment могао бити сачуван кроз setRetainInstance(true) при ротацији екрана. Овај приступ је замењен ViewModel + SavedStateHandle, који раде поузданије и не захтевају посебно подешавање.
viewLifecycleOwner — Lifecycle везан за View Fragment-а (од onCreateView до onDestroyView). Ово је концептуално важан концепт: претплате на LiveData/Flow направљене кроз viewLifecycleOwner аутоматски се отказују при уништењу View-а (onDestroyView), али не утичу на сам Fragment.
Разлика између viewLifecycleOwner-а и lifecycle-а Fragment-а:
Зашто је ово важно: ако се претплатите на LiveData кроз lifecycle Fragment-а (this), након onDestroyView претплата остаје активна и LiveData ће покушати да ажурира null View, изазивајући NPE. Претплата кроз viewLifecycleOwner гарантује да након onDestroyView неће доћи до никаквих UI ажурирања.
Правило: у Fragment-у увек користите viewLifecycleOwner за претплате на LiveData, Flow и корутине повезане са UI-јем. За корутине ViewModel-а користите viewModelScope — он је везан за ViewModel, а не за Fragment.
Демонстрира исправну иницијализацију UI-ја и претплату на LiveData кроз 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", "Ажурирање листе: ${users.size} корисника")
}
}
override fun onDestroyView() {
super.onDestroyView()
Log.d("UserListFragment", "onDestroyView: View уништен")
}
}
Fragment надувава layout у onCreateView, подешава UI и претплаћује се на LiveData у onViewCreated. Претплата кроз viewLifecycleOwner — обавезан захтев за спречавање цурења меморије. onDestroyView логира уништење View-а — потврда да Fragment преживљава ротацију екрана.
Демонстрира додавање Fragment-а кроз FragmentManager у Activity-ју, замену са BackStack-ом и обнављање.
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", "Учитавање детаља корисника: $userId")
}
}
Activity користи supportFragmentManager за управљање фрагментима. Трансакција add() са BackStack-ом гарантује да ће се при притиску „Назад“ HomeFragment обновити. openDetail() замењује тренутни Fragment са DetailFragment-ом са аргументима. Провера savedInstanceState == null спречава дуплирање фрагмената при ротацији екрана.
Коришћење Flow и StateFlow у Fragment-у са viewLifecycleOwner за реактивно ажурирање 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", "Резултати претраге: ${results.size}")
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
Fragment користи View Binding за приступ View-у. Корутина viewLifecycleOwner.lifecycleScope.launch се аутоматски отказује при уништењу View-а. Binding се поништава у onDestroyView ради спречавања цурења меморије. StateFlow гарантује актуелност података при поновном креирању View-а.
Често постављана питања
onCreateView — креира и враћа основни View Fragment-а. onViewCreated — позива се одмах након креирања View-а, гарантује да је View потпуно иницијализован и спреман за подешавање (findViewById, претплате). Google препоручује да у onCreateView само надувавате layout, а целокупно подешавање UI-ја радите у onViewCreated.
onDestroy — Fragment је уништен као објекат (ViewModel се чисти, корутине се отказују). onDetach — последњи повратни позив, након којег се Fragment одваја од Activity-ја. Практично сви ресурси треба да буду ослобођени у onDestroyView (View) и onDestroy (Fragment). onDetach — за чишћење референци на Activity.
Fragment нестаје ако није додат у FragmentManager кроз трансакцију са чувањем у BackStack или ако Activity не обнавља FragmentManager у onCreate-у. Решење: додајте Fragment програмски кроз supportFragmentManager.beginTransaction().add() у onCreate-у са провером savedInstanceState == null.
Не. Fragment је увек везан за Activity кроз FragmentManager. Чак и при ротацији екрана, Activity се поново креира, а Fragment се поново везује за нови Activity. Креирање Fragment-а ван Activity-ја је немогуће — конструктор Fragment-а захтева празан конструктор за обнављање од стране система.
Nested fragments (угнежђени фрагменти) — Fragment унутар другог Fragment-а. Користе се за изградњу сложених екрана: таб панели, панели са картицама, master-detail. Угнежђени фрагменти се управљају кроз подређени FragmentManager (childFragmentManager). Google препоручује да не прелазите 2 нивоа угнежђености како бисте избегли проблеме са перформансама.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође