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) والعرض الخاص به (يعيش من onCreateView إلى onDestroyView). هذا الفصل هو ميزة رئيسية لـ Fragment، حيث تسمح له بالبقاء بعد تدمير العرض عند تدوير الشاشة دون تدمير Fragment نفسه.
التسلسل الكامل لاسترجاعات Fragment:
وفقاً لـ Google، يمر متوسط fragment في التطبيق الحديث بالدورة الكاملة من 3 إلى 5 مرات لكل جلسة مستخدم (بسبب تدوير الشاشة والتنقل). المعالجة الصحيحة لجميع المراحل هي أساس استقرار واجهة المستخدم.
يدير FragmentManager Fragment عبر خمس حالات رئيسية، محددة في الفئة Fragment.State. كل حالة تتوافق مع مجموعة محددة من الاسترجاعات التي تم تنفيذها.
| الحالة | المعنى | الاسترجاعات المنفذة |
|---|---|---|
| INITIALIZED | تم إنشاء Fragment، لكن العرض غير متوفر بعد | onAttach، onCreate |
| CREATED | تم إنشاء العرض، لكن 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 خاصة: قد يتم تدمير العرض (بعد onDestroyView)، لكن Fragment نفسه يبقى في حالة CREATED (بعد onDestroyView، قبل onDestroy). يسمح هذا لـ FragmentManager بالاحتفاظ بـ Fragment في الذاكرة دون عرض، وهو أمر ضروري للبقاء بعد تدوير الشاشة.
يرتبط Fragment Lifecycle و Activity Lifecycle ارتباطاً وثيقاً لكن لديهما اختلافات أساسية. يعيش Fragment دائماً داخل Activity، وتعتمد دورة حياته على Activity المضيفة، لكنها ليست مطابقة لها.
| الجانب | Activity | Fragment |
|---|---|---|
| عدد الاسترجاعات | 7 (onCreate … onDestroy) | 11 (onAttach … onDetach) |
| دورة حياة منفصلة للعرض | لا | نعم (viewLifecycleOwner) |
| البقاء بعد التدوير | لا (يتم تدميره) | نعم (ViewModel + Fragment يبقيان) |
| الاعتماد على المضيف | لا | يعتمد على Activity Lifecycle |
| حفظ الحالة | onSaveInstanceState | onSaveInstanceState (على مستوى Fragment) |
| الإدارة | النظام | FragmentManager |
الفرق العملي الرئيسي: عند تدوير الشاشة، يتم تدمير Activity بالكامل (onDestroy) وإعادة إنشائها (onCreate). Fragment أثناء التدوير يمر عبر onDestroyView (يتم تدمير العرض) ← onCreateView (يتم إعادة إنشاء العرض)، لكن Fragment نفسه و ViewModel يبقيان على قيد الحياة. هذا يجعل Fragment حاوية مثالية لمنطق واجهة المستخدم الذي يجب أن يبقى بعد تغييرات التكوين.
ترتيب الاستدعاءات أثناء تدوير الشاشة: 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 هو الفئة المركزية المسؤولة عن إضافة وإزالة واستبدال fragments وإدارة حالاتها. يحتفظ 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 ويستعيد البيانات تلقائياً عند تدوير الشاشة وموت العملية، دون الحاجة إلى onSaveInstanceState يدوي. يوصي Google باستخدام SavedStateHandle كالطريقة المفضلة لحفظ حالة واجهة المستخدم في Fragment.
setRetainInstance (مهمل منذ Fragment 1.3): سابقاً كان يمكن الاحتفاظ بـ Fragment عبر setRetainInstance(true) عند تدوير الشاشة. تم استبدال هذا النهج بـ ViewModel + SavedStateHandle، اللذان يعملان بشكل أكثر موثوقية ولا يتطلبان تكويناً خاصاً.
viewLifecycleOwner هو Lifecycle مرتبط بعرض Fragment (من onCreateView إلى onDestroyView). هذا مفهوم مهم جداً: الاشتراكات في LiveData/Flow التي تتم عبر viewLifecycleOwner تُلغى تلقائياً عند تدمير العرض (onDestroyView)، لكنها لا تؤثر على Fragment نفسه.
الفرق بين viewLifecycleOwner و lifecycle Fragment:
لماذا هذا مهم: إذا اشتركت في LiveData عبر lifecycle Fragment (this)، فبعد onDestroyView يبقى الاشتراك نشطاً وسيحاول LiveData تحديث عرض فارغ، مسبباً NPE. الاشتراك عبر viewLifecycleOwner يضمن عدم حدوث أي تحديثات لواجهة المستخدم بعد onDestroyView.
القاعدة: في Fragment، استخدم دائماً viewLifecycleOwner للاشتراكات في LiveData و Flow و coroutines المتعلقة بواجهة المستخدم. لـ coroutines الخاصة بـ ViewModel، استخدم viewModelScope — فهو مرتبط بـ ViewModel، وليس بـ Fragment.
يوضح التهيئة الصحيحة لواجهة المستخدم والاشتراك في 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: تم تدمير العرض")
}
}
يقوم Fragment بتضخيم التخطيط في onCreateView، وتكوين واجهة المستخدم والاشتراك في LiveData في onViewCreated. الاشتراك عبر viewLifecycleOwner هو شرط إلزامي لمنع تسرب الذاكرة. يسجل onDestroyView تدمير العرض — تأكيداً على أن 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 لإدارة fragments. تضمن معاملة add() مع BackStack أنه عند الضغط على رجوع يتم استعادة HomeFragment. يقوم openDetail() باستبدال Fragment الحالي بـ DetailFragment مع وسائط. يمنع التحقق من savedInstanceState == null تكرار fragments عند تدوير الشاشة.
استخدام Flow و StateFlow في Fragment مع viewLifecycleOwner للتحديثات التفاعلية لواجهة المستخدم.
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 للوصول إلى العرض. يتم إلغاء coroutine viewLifecycleOwner.lifecycleScope.launch تلقائياً عند تدمير العرض. يتم إلغاء binding في onDestroyView لمنع تسرب الذاكرة. يضمن StateFlow تحديث البيانات عند إعادة إنشاء العرض.
الأسئلة الشائعة
onCreateView ينشئ ويعيد العرض الجذر لـ Fragment. onViewCreated يتم استدعاؤه فور إنشاء العرض، مما يضمن أن العرض مهيأ بالكامل وجاهز للتكوين (findViewById، الاشتراكات). توصي Google فقط بتضخيم التخطيط في onCreateView، والقيام بكل تكوين واجهة المستخدم في onViewCreated.
onDestroy — يتم تدمير Fragment ككائن (يتم مسح ViewModel وإلغاء coroutines). onDetach هو آخر استرجاع، وبعده ينفصل Fragment عن Activity. عملياً، يجب تحرير جميع الموارد في onDestroyView (العرض) و 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 يتطلب مُنشئاً فارغاً لاستعادة النظام.
Fragments المتداخلة (nested fragments) هي Fragments داخل Fragment آخر. تُستخدم لبناء شاشات معقدة: ألواح علامات التبويب، لوحات ذات ألسنة، master-detail. تُدار fragments المتداخلة بواسطة FragmentManager التابع (childFragmentManager). توصي Google بعدم تجاوز مستويين من التداخل لتجنب مشاكل الأداء.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا