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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також