DisposableEffect — تحرير الموارد في Jetpack Compose

المؤلف: IT Sectr نُشر: 2026-06-30 وقت القراءة: 9 دق

DisposableEffect هي دالة composable في Jetpack Compose مصممة للعمليات التي تتطلب تهيئة صريحة وتنظيفاً لاحقاً للموارد. على عكس أبراج API الآثار الجانبية الأخرى، يوفر DisposableEffect كتلة onDispose التي تتنفذ بضمان عند خروج المكوّن من التركيب أو عند تغير المفتاح. هذا يجعله لا غنى عنه للعمل مع الاشتراكات الأصلية ومستمعي الحساسات وموارد الأجهزة. وفقاً لـ Android Developers Documentation (2025)، يوصى باستخدام DisposableEffect في جميع السيناريوهات التي تتطلب زوجاً setup/teardown، مماثلاً لـ onStart/onStop في دورة حياة Activity.

النقاط الرئيسية

  • DisposableEffect — API آثار جانبية للإعداد والتنظيف المضمون للموارد.
  • onDispose — كتلة إجبارية تتنفذ عند الخروج من التركيب أو تغيير المفتاح.
  • التزامن — على عكس LaunchedEffect، يعمل DisposableEffect بشكل متزامن دون كوروتين.
  • التنظيف — السيناريوهات النموذجية: إلغاء الاشتراك من LiveData، إغلاق المقابس، إلغاء تسجيل BroadcastReceiver.
  • المفاتيح — عند تغيير المفتاح، يتم تنفيذ onDispose للقيمة القديمة وإعادة التهيئة بالقيمة الجديدة.

ما هو DisposableEffect في Jetpack Compose

DisposableEffect هي أداة رئيسية لإدارة الموارد في Jetpack Compose. ميزتها الرئيسية هي الاستدعاء المضمون لكتلة onDispose عند انتهاء دورة حياة المكوّن القابل للتركيب. هذا السلوك بالغ الأهمية لتطوير Android، حيث يمكن للاشتراكات غير المغلقة في خدمات النظام أن تؤدي إلى تسرب الذاكرة وإعادات التطبيق غير المتوقعة.

على عكس LaunchedEffect، الذي يعمل في سياق غير متزامن للكوروتين، يتنفذ DisposableEffect بشكل متزامن. وهذا يعني أنه لا يمكن استدعاء دوال suspend داخله. يضمن التنفيذ المتزامن القابلية للتوقع: يمكنك التأكد من أن كود التهيئة سيتنفذ قبل أول عرض، وكود التنظيف قبل إزالة المكوّن من الذاكرة.

وفقاً لـ توثيق Jetpack Compose (2025)، يجب استخدام DisposableEffect في أربعة سيناريوهات رئيسية: (1) الاشتراك في خدمات النظام (الحساسات، LocationManager)، (2) تسجيل BroadcastReceiver، (3) العمل مع مكتبات قائمة على callback التي لا تدعم الكوروتين، (4) ربط مكوّنات Compose بأنظمة View القديمة عبر AndroidView.

kotlin
class SensorManager(private val context: Context) {
    fun startListening(callback: (Float) -> Unit) { /* register */ }
    fun stopListening() { /* cancel */ }
}

@Composable
fun SensorDisplay() {
    val sensorManager = remember { SensorManager(context) }
    var value by remember { mutableStateOf(0f) }
    
    DisposableEffect(Unit) {
        sensorManager.startListening { value = it }
        onDispose { sensorManager.stopListening() }
    }
    
    Text("الحساس: $value")
}

كيف يعمل DisposableEffect مع onDispose

تعتمد الآلية الداخلية لـ DisposableEffect على مراحل دورة حياة التركيب. عندما يدخل المكوّن القابل للتركيب إلى التركيب، يقوم DisposableEffect بتنفيذ كتلة الكود الممررة. تعيد هذه الكتلة كائن DisposableEffectResult يحتوي على lambda onDispose. تحتفظ التركيبة بهذه النتيجة وتستدعي onDispose عندما يغادر المكوّن التركيب — بغض النظر عن السبب (التنقل، تغيير حالة الأصل، الإزالة من LazyColumn).

تعمل آلية المفاتيح في DisposableEffect بشكل مماثل لـ LaunchedEffect: عند تغيير أي مفتاح، يتم أولاً تنفيذ onDispose للحالة القديمة، ثم يتم تشغيل كتلة التهيئة مرة أخرى بالمفاتيح الجديدة. هذا يسمح بإعادة تكوين المورد عند تغيير معلماته. على سبيل المثال، إذا كان المفتاح هو URL المقبس، عند تغييره يتم إغلاق المقبس القديم وفتح مقبس جديد.

مهم: كتلة onDispose هي عنصر إجباري في DisposableEffect. إذا لم يتم استدعاء onDispose داخل الكتلة، لن يتم تجميع الكود. يضمن هذا متطلب المجمع عدم نسيان المطور لتوفير تنظيف المورد، وهو سبب شائع للأخطاء في إدارة الاشتراكات اليدوية.

kotlin
// الاستخدام الصحيح مع مفتاح
DisposableEffect(sensorType) {
    val sensor = sensorManager.getDefaultSensor(sensorType)
    sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
    
    onDispose {
        sensorManager.unregisterListener(listener)
    }
}

// موارد متعددة في DisposableEffect واحد
DisposableEffect(Unit) {
    context.registerReceiver(receiver, intentFilter)
    lifecycle.addObserver(observer)
    
    onDispose {
        context.unregisterReceiver(receiver)
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect ضد تسرب الذاكرة

كثيراً ما تنشأ تسربات الذاكرة في تطبيقات Android نتيجة للمستمعين غير المسجلين والاشتراكات التي تظل تحتفظ بمرجع إلى Activity أو Context بعد إغلاق الشاشة. DisposableEffect يحل هذه المشكلة على مستوى الإطار: إذا استخدم المطور DisposableEffect لتسجيل مستمع، سيضمن onDispose إلغاء الاشتراك في أي سيناريو لإنهاء المكوّن.

هذا هو الأمر الحاسم بشكل خاص لـ LazyColumn و LazyGrid، حيث يتم إنشاء العناصر وتدميرها بشكل مستمر أثناء التمرير. بدون DisposableEffect، كل عنصر يختفي من المنطقة المرئية سيترك اشتراكاً نشطاً. مع DisposableEffect، يتم استدعاء onDispose لكل عنصر مفرغ، مما يضمن تحرير الموارد فور خروج العنصر من الشاشة.

وفقاً لـ Android Performance Patterns (Google, 2025)، يقلل استخدام DisposableEffect لجميع الاشتراكات الأصلية عدد تسربات الذاكرة في تطبيقات Compose بنسبة 60–70% مقارنة بالإدارة اليدوية عبر callbacks دورة الحياة. يتبع النظام نفسه لحظة خروج المكوّن من التركيب ويضمن تنفيذ onDispose حتى أثناء الإغلاق الطارئ للشاشة.

الموردما يفعله DisposableEffectبدون DisposableEffect
BroadcastReceiverregister + onDispose → unregisterيبقى المستقبل نشطاً
SensorManagerregisterListener + onDispose → unregisterListenerيستمر الحساس في إرسال البيانات
Observable (ليس Flow)subscribe + onDispose → unsubscribeيحتفظ Callback بالمرجع
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackتسريب Callback
Socket / Channelopen + onDispose → closeيبقى الاتصال مفتوحاً

الاشتراك في الحساسات عبر DisposableEffect

أحد أكثر الأمثلة توضيحاً لاستخدام DisposableEffect هو العمل مع حساسات الجهاز (مقياس التسارع، الجيروسكوب، مقياس المغناطيسي). تتطلب الحساسات إلغاء التسجيل إجبارياً عند الانتهاء، وإلا ستستمر في استهلاك طاقة البطارية وإرسال البيانات حتى بعد إغلاق الشاشة.

مثال عملي: تطبيق لقياس زاوية الميل. يقوم DisposableEffect(Unit) بتسجيل مستمع مقياس التسارع عند ظهور المكوّن وإلغاء تسجيله في onDispose. تُنقل بيانات الحساس إلى الحالة عبر mutableStateOf، مما يقوم بتحديث واجهة المستخدم تلقائياً. إذا تم التمرير في LazyColumn واختفى العنصر، ينطلق onDispose فوراً — يتوقف الحساس عن إرسال البيانات لهذا العنصر.

عند تغيير نوع الحساس (على سبيل المثال، من مقياس التسارع إلى الجيروسكوب)، يتغير المفتاح sensorType، يلغي onDispose الاشتراك القديم، ويسجل بلوك DisposableEffect الجديد الحساس الجديد. بدون المفاتيح، سيتعين عليك التحقق يدوياً من الحساس الذي تم تسجيله سابقاً واستدعاء unregisterListener مع المستمع الصحيح — وهو أمر عرضة للأخطاء.

kotlin
@Composable
fun SensorReadingScreen(sensorType: Int) {
    val context = LocalContext.current
    val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
    var sensorValue by remember { mutableStateOf(0f) }
    
    DisposableEffect(sensorType) {
        val sensor = sensorManager.getDefaultSensor(sensorType)
        val listener = SensorEventListener { event, _ ->
            sensorValue = event.values[0]
        }
        sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
        
        onDispose {
            sensorManager.unregisterListener(listener)
        }
    }
    
    Text("القيمة: $sensorValue")
}

تسجيل BroadcastReceiver عبر DisposableEffect

BroadcastReceiver هو مثال كلاسيكي لـ API يتطلب زوجاً إجبارياً register / unregister. في تطبيق Compose، يعتبر DisposableEffect مثالياً لتسجيل مستقبل لمداة حياة شاشة محددة. عند الدخول إلى الشاشة، يتم تسجيل BroadcastReceiver مع IntentFilter المطلوب؛ عند الخروج، يتم إلغاؤه تلقائياً في onDispose.

السيناريو النموذجي هو مراقبة حالة الشبكة. يسجل DisposableEffect مستقبلاً لـ ConnectivityManager يُخطر بتغييرات اتصال الشبكة. عند تغيير الحالة (WiFi / بيانات تنقل / بدون شبكة)، يتم تحديث حالة composable وتعرض UI المؤشر المناسب. عند إغلاق الشاشة، يضمن onDispose إلغاء التسجيل — حتى إذا انتقل التطبيق إلى الخلفية.

بالنسبة للمستقبلات التي تستخدم ContextCompat.registerReceiver مع علمة RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+)، يصبح استخدام DisposableEffect إجبارياً لأن النظام يتطلب تحديداً صريحاً لنطاق المستقبل. يضمن DisposableEffect أن النطاق مقتصر على عمر الشاشة، مما يتوافق مع متطلبات الأمان لإصدارات Android الأحدث.

kotlin
@Composable
fun NetworkStatusBanner() {
    val context = LocalContext.current
    var isConnected by remember { mutableStateOf(true) }
    
    DisposableEffect(Unit) {
        val receiver = BroadcastReceiver { _, _ ->
            val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            isConnected = cm.getActiveNetwork() != null
        }
        IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).let { filter ->
            context.registerReceiver(receiver, filter)
        }
        
        onDispose {
            context.unregisterReceiver(receiver)
        }
    }
    
    if (!isConnected) { ... }
}

الأخطاء الشائعة مع DisposableEffect

الخطأ الأول والحرج هو عدم استدعاء onDispose. يجب على الكود داخل كتلة DisposableEffect استدعاء onDispose، وإلا سيحدث خطأ في التجميع. ولكن في بعض الأحيان يحاول المطورون تجاوز هذا بوضع onDispose في شرط: if (condition) { onDispose { ... } }. سيتم تجميع هذا الكود، ولكن لن يتم تسجيل onDispose إذا لم يتحقق الشرط — لن يتم تحرير المورد أبداً.

الخطأ الثاني هو استخدام DisposableEffect للعمليات غير المتزامنة. نظرًا لأن DisposableEffect متزامن، لا يمكن كتابة استدعاءات delay() أو await() داخله. إذا كنت تحتاج إلى تهيئة غير متزامنة مع تنظيف لاحق، استخدم مزيجاً من LaunchedEffect (لتحميل البيانات) و DisposableEffect (لإعداد/تنظيف الموارد الأصلية)، أو استخدم آلية منفصلة مع rememberCoroutineScope.

الخطأ الثالث هو إنشاء كائنات جديدة داخل DisposableEffect بدون remember. إذا تم إنشاء الكائنات (حساس، listener، receiver) داخل التأثير في كل استدعاء وتتغير المفاتيح بشكل متكرر، فهذا يؤدي إلى إنشاء مفرط للكائنات وجمع القمامة. من الأفضل نقل إنشاء الكائنات إلى remember أو remember { ... } خارج DisposableEffect، وفقط تسجيلها وإلغاء تسجيلها داخل التأثير.

الأسئلة الشائعة

ما الفرق بين DisposableEffect و LaunchedEffect؟

DisposableEffect يعمل بشكل متزامن ويوفر onDispose للتنظيف الصريح للموارد. يعمل LaunchedEffect بشكل غير متزامن في كوروتين ويلغيها تلقائياً عند تغيير المفتاح أو الخروج من التركيب. إذا كان المورد يتطلب استدعاء طريقة تنظيف (close، unregister، dispose) — استخدم DisposableEffect. إذا كانت العملية دالة suspend — استخدم LaunchedEffect.

هل كتلة onDispose إجبارية في DisposableEffect؟

نعم، onDispose إجباري — يتطلب مجمع Kotlin استدعاءه داخل كتلة DisposableEffect. إذا لم يتم استدعاء onDispose، لن يتم تجميع الكود. هذا متعمد لمنع نسيان المطورين وضمان إغلاق كل مورد مفتوح بشكل مضمون عند الخروج من التركيب.

كيف نتعامل مع الأخطاء داخل DisposableEffect؟

استخدم try-catch داخل كتلة DisposableEffect. إذا كان تسجيل المورد قد يتح استثناءً (على سبيل المثال، الحساس غير موجود)، الف بـ try وعالج الخطأ في UI عبر حالة منفصلة. يجب استدعاء onDispose بغض النظر عن نجاح التهيئة — ضعه في كتلة finally أو في نهاية قسم try.

هل يمكنني استخدام DisposableEffect للاشتراك في Flow؟

غير موصى به. لـ Flow، من الأفضل استخدام LaunchedEffect مع collectLatest أو طريقة .collectAsState() مع Lifecycle.repeatOnLifecycle. DisposableEffect لا يدعم دوال suspend، لذا فإن الاشتراك في Flow داخله سيتطلب تشغيل كوروتين منفصل عبر CoroutineScope، مما يعقد الكود ويزيد خطر التسريبات.

كم عدد DisposableEffect الممكن في composable واحد؟

لا توجد حدود، ولكن يُوصى بتجميع الموارد المرتبطة في DisposableEffect واحد مع عدة عمليات داخله و onDispose واحد. إذا كانت الموارد مستقلة (على سبيل المثال، حساس و BroadcastReceiver)، من الأفضل تقسيمها إلى DisposableEffects منفصلة بمفاتيح مختلفة — هذا يبسط التصحيح ويمنع إعادة إنشاء جميع الموارد غير المرغوب فيه عند تغيير مفتاح واحد.

الملخص

  • DisposableEffect — API آثار جانبية في Jetpack Compose للتهيئة المتزامنة مع تنظيف مضمون عبر onDispose.
  • onDispose — كتلة إجبارية تتنفذ عند الخروج من التركيب أو تغيير المفتاح، وتمنع تسرب الذاكرة.
  • المفاتيح — عند تغيير المفتاح، يتم أولاً تنفيذ onDispose للقيمة القديمة، ثم إعادة التهيئة بالقيمة الجديدة.
  • متزامن — يتنفذ DisposableEffect بشكل متزامن؛ دوال suspend غير متاحة داخله.
  • السيناريوهات النموذجية — BroadcastReceiver، الحساسات، المستمعين الأصليين، مكتبات قائمة على callback، التكامل مع AndroidView.
  • التسريبات — يقلل DisposableEffect عدد التسريبات بنسبة 60–70% مقارنة بالإدارة اليدوية لـ callbacks دورة الحياة.
  • الأخطاء — المخاطر الرئيسية: استدعاء onDispose بشرطي، استخدامه للعمليات غير المتزامنة، إنشاء كائنات بدون remember داخل التأثير.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا