ActivityResultLauncher: ما هو وكيفية استخدامه

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

ActivityResultLauncher هو مكون من Android Activity Result API، تم تقديمه في الإصدار Activity 1.2.0 من مكتبة androidx.activity. يحل محل الطريقتين القديمتين startActivityForResult و onActivityResult، اللتين كانتا جزءًا من Android SDK منذ إنشائه. وفقًا لـ Android Developers (2024)، فإن API الجديد يزيل مشاكل الارتباط القوي مع Activity وغياب أمان الأنواع. ActivityResultLauncher يتم تسجيله مسبقًا ويستخدم Contract لكتابة صارمة لأنواع البيانات المدخلة والمخرجة.

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

  • ActivityResultLauncher — API جديدة للحصول على نتائج من Activity بدلاً من startActivityForResult القديم
  • Contract — كائن يحدد نوع بيانات الإدخال والإخراج لسيناريو معين
  • التسجيل يتم عبر registerForActivityResult قبل استدعاء launch
  • Callback يتم استدعاؤه بعد انتهاء Activity المستهدفة بنتيجة
  • API متاح لـ Activity و Fragment و Compose بدءًا من Activity 1.2.0

ما هو ActivityResultLauncher

ActivityResultLauncher هو فئة من حزمة androidx.activity.result توفر آلية آمنة من حيث الأنواع لتشغيل Activity والحصول على النتيجة. يتم إنشاء launcher عبر طريقة registerForActivityResult، التي تأخذ معلمتين: Contract (يصف أنواع الإدخال والإخراج) و ActivityResultCallback (معالج النتيجة). بعد التسجيل، يكون launcher جاهزًا للاستدعاء عبر طريقة launch.

الفرق الرئيسي عن API القديم هو فصل التسجيل عن التشغيل. يتم التسجيل في مرحلة التهيئة (Activity.onCreate أو Fragment.onCreate)، ويتم ربط callback مع launcher مرة واحدة ويتم ضمان استدعائه عند عودة النتيجة. هذا يزيل مشكلة استدعاء onActivityResult بترتيب غير متوقع أو على Activity مدمرة.

يدعم ActivityResultLauncher جميع السيناريوهات التي كانت تُعالج سابقًا عبر onActivityResult: تشغيل الكاميرا، المعرض، طلب جهات الاتصال، الأذونات، و Activities المخصصة. بالإضافة إلى ذلك، API قابل للتوسيع: يمكن للمطورين إنشاء Contracts مخصصة لسيناريوهات محددة لتبادل البيانات بين Activities.

لماذا استبدل Activity Result API startActivityForResult

startActivityForResult كان جزءًا من Android SDK منذ API Level 1 (2008) وبقي الطريقة الرئيسية للحصول على نتيجة من Activity لأكثر من 12 عامًا. ومع ذلك، كانت لهذه الطريقة عيوب أساسية عالجتها Google في Activity Result API. دعنا نلقي نظرة على المشكلات الرئيسية وكيف يحلها API الجديد.

المشكلة 1: الارتباط القوي مع Activity

طريقة startActivityForResult مرتبطة بـ Activity و Fragment عبر requestCode — رقم صحيح عشوائي يُمرر إلى onActivityResult. كان المطور يطابق الكود يدويًا مع العملية المشغلة، مما يؤدي إلى أخطاء في إعادة استخدام الكود والوراثة. ActivityResultLauncher يلغي requestCode تمامًا: يتم ربط callback مع launcher محدد في وقت التسجيل ويتم استدعاؤه فقط له.

المشكلة 2: فقدان النتيجة عند تدوير الشاشة

أثناء تغييرات التكوين (تدوير الشاشة، تغيير اللغة)، يتم إعادة إنشاء Activity وقد لا يتم استدعاء onActivityResult — يتم فقدان callback. Activity Result API يحفظ ويستعيد حالة launcher تلقائيًا عبر SavedStateRegistry، مما يضمن استلام النتيجة حتى بعد إعادة إنشاء Activity.

المشكلة 3: غياب أمان الأنواع

API القديم يمرر النتيجة عبر Intent مع Bundle، حيث لا يتم التحقق من المفاتيح وأنواع البيانات بواسطة المترجم. Activity Result API يستخدم Contract — واجهة عامة تحدد نوع بيانات الإدخال (I) ونوع النتيجة (O). يتم اكتشاف أخطاء عدم تطابق الأنواع في وقت الترجمة، وليس في وقت التشغيل.

الخاصيةstartActivityForResultActivityResultLauncher
RequestCodeإدارة يدوية مطلوبةتلقائي، غير مطلوب
أمان الأنواعلاContract عام
الحفظ عند التدويريُفقدSavedStateRegistry
أدنى APIAPI Level 1Activity 1.2.0
الاستخدام في Composeغير مدعومrememberLauncherForActivityResult

العقود الرئيسية لـ Activity Result API

Contract هو واجهة ActivityResultContract<I, O>، التي تحدد كيفية تشغيل Activity وكيفية تفسير النتيجة. توفر Google مجموعة من العقود المدمجة للسيناريوهات النموذجية التي تغطي معظم احتياجات المطور.

StartIntentSenderForResult

StartIntentSenderForResult — عقد أساسي لتشغيل IntentSender. يُستخدم في السيناريوهات النظامية، على سبيل المثال عند التفويض عبر Google Sign-In أو الدفع عبر Google Pay. معلمة الإدخال هي PendingIntent، والإخراج هو ActivityResult مع الكود و Intent.

RequestMultiplePermissions

RequestMultiplePermissions — عقد لطلب أذونات متعددة في وقت واحد على Android 6.0+. معلمة الإدخال هي مصفوفة String بأسماء الأذونات، والإخراج هو Map<String, Boolean> بنتيجة كل طلب. سابقًا كان هذا يتطلب تحليلًا يدويًا في onRequestPermissionsResult مع مطابقة رموز الطلبات.

TakePicture و TakeVideo

TakePicture — عقد لالتقاط صورة عبر كاميرا النظام. الإدخال هو Uri لحفظ الصورة، والإخراج هو Boolean (نجاح). TakeVideo يعمل بالمثل مع الفيديو. تحل هذه العقود محل MediaStore.ACTION_IMAGE_CAPTURE القديم بسلوك غير مستقر على الأجهزة المختلفة.

GetContent و OpenDocument

GetContent — عقد لاختيار المحتوى عبر منتقي النظام. الإدخال هو نوع MIME (مثل image/*)، والإخراج هو Uri للملف المحدد. OpenDocument يختلف بدعمه للتحديد المتعدد والتصفية حسب أنواع المستندات. يعمل كلا العقدين عبر SAF (Storage Access Framework).

CreateDocument و OpenDocumentTree

CreateDocument — عقد لإنشاء مستند جديد عبر حوار النظام. يختار المستخدم اسمًا ومجلدًا، ويعيد النظام Uri للكتابة. OpenDocumentTree يوفر الوصول إلى دليل كامل — يختار المستخدم مجلدًا، ويتلقى التطبيق tree-uri لقراءة وكتابة جميع الملفات داخله.

الاستخدام في Activity و Fragment

النمط الأساسي لاستخدام ActivityResultLauncher في Android التقليدي يتكون من خطوتين: التسجيل عبر registerForActivityResult في مرحلة التهيئة واستدعاء launch استجابةً لإجراء المستخدم. دعنا نلقي نظرة على مثال نموذجي لاختيار صورة من المعرض.

التسجيل والتشغيل في Activity

سجل launcher في onCreate الخاص بـ Activity — هذا يضمن أن callback جاهز قبل أي استدعاء محتمل. لا تسجل launcher قبل التشغيل مباشرة — هذا ينتهك عقد API ويمكن أن يؤدي إلى فقدان النتيجة عند إعادة إنشاء Activity.

kotlin
class MainActivity : AppCompatActivity() {
    private val pickImageLauncher =
        registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
            uri?.let { binding.imageView.setImageURI(it) }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pickImageLauncher.launch("image/*")
    }
}

الاستخدام في Fragment

في Fragment، يتم التسجيل في onCreate أو onAttach أو التهيئة في onCreateView. FragmentActivity يمرر launcher عبر Activity الأصلية، لذلك تتم معالجة النتيجة داخل Fragment، وليس في Activity. هذا يحسن التغليف مقارنة بـ onActivityResult، حيث كانت جميع النتائج من جميع Fragments تُجمع في طريقة واحدة في Activity.

kotlin
class ProfileFragment : Fragment() {
    private val cameraLauncher =
        registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
            if (success) { updateProfilePhoto() }
        }

    fun takePhoto(photoUri: Uri) {
        cameraLauncher.launch(photoUri)
    }
}

ActivityResultLauncher في Jetpack Compose

Jetpack Compose يوفر دالة composable خاصة لـ Activity Result API — rememberLauncherForActivityResult. على عكس النهج التقليدي، في Compose يتم إنشاء launcher ككائن مرتبط بدورة حياة composable عبر remember. هذا يسمح باستخدام Activity Result API بالكامل في أسلوب تصريحي دون وصول مباشر إلى Activity أو Fragment.

rememberLauncherForActivityResult

rememberLauncherForActivityResult يأخذ Contract و callback، ويعيد ActivityResultLauncher. يتم الحفاظ على launcher أثناء إعادة التركيب ويتم تنظيفه تلقائيًا عند الخروج من التركيب. يحدث استدعاء launch استجابة لحدث — على سبيل المثال، نقرة زر أو تغيير حالة.

kotlin
@Composable
fun PhotoPicker() {
    val context = LocalContext.current
    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri -> handleImage(uri) }

    Button(onClick = { launcher.launch("image/*") }) {
        Text("اختيار صورة")
    }
}

معالجة الأذونات في Compose

طلبات الأذونات في Compose تتم أيضًا عبر rememberLauncherForActivityResult مع عقد RequestPermission أو RequestMultiplePermissions. توصي Google باستخدام accompanist-permissions، لكنه داخليًا يستخدم أيضًا Activity Result API. للتحكم في حالة الأذونات، من المناسب تخزين الحالة في remember أو ViewModel.

الأخطاء الشائعة وأفضل الممارسات

Activity Result API أزال العديد من مشكلات النهج القديم، لكن الاستخدام غير الصحيح يمكن أن يؤدي إلى أنواع جديدة من الأخطاء. دعنا نلقي نظرة على المشكلات الأكثر شيوعًا وكيفية تجنبها.

خطأ: التسجيل داخل lambda أو coroutine

تسجيل launcher يجب أن يتم أثناء تهيئة المكون — في onCreate لـ Activity أو مُهيئ Fragment. إذا سجلت launcher داخل lambda أو callback أو coroutine، عند إعادة إنشاء Activity قد يتم التسجيل مرة أخرى ويفقد launcher القديم الاتصال بالنتيجة.

خطأ: تسجيل عدة launchers بنفس المفتاح

كل launcher يحصل على مفتاح فريد لحفظ الحالة. إذا سجلت اثنين من launchers بنفس Contract في مكون واحد، قد يقوم SavedStateRegistry باستبدال حالة أحدهما بالآخر. يحذر Android Studio من هذا عبر قاعدة lint UnnecessaryRegisterForActivityResult، لكن من الأفضل التحكم في التفرد يدويًا.

أفضل ممارسة: معالجة النتيجة الفارغة دائمًا

قد يقوم المستخدم بإلغاء الإجراء — الضغط على زر الرجوع النظامي، تصغير التطبيق، أو التبديل إلى تطبيق آخر. في هذه الحالة، سيتلقى callback قيمة null أو ActivityResult مع RESULT_CANCELED. تحقق دائمًا من أن النتيجة ليست null قبل استخدامها لتجنب NullPointerException.

أفضل ممارسة: Contracts مخصصة للمنطق القابل لإعادة الاستخدام

إذا كان تطبيقك يشغل بشكل متكرر سيناريوهات متشابهة — على سبيل المثال، اختيار جهة اتصال وإرجاع الاسم والهاتف — قم بإنشاء Contract مخصص. هذا يحسن قابلية قراءة الكود ويسمح بتغييرات مركزية في منطق التشغيل ومعالجة النتائج.

kotlin
class PickContactContract : ActivityResultContract<Void, ContactData?>() {
    override fun createIntent(context: Context, input: Void?) =
        Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE)

    override fun parseResult(resultCode: Int, intent: Intent?) =
        intent?.data?.let { queryContact(it) }
}

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

هل يمكن استخدام ActivityResultLauncher في ViewModel؟

لا — ActivityResultLauncher يتطلب سياق Activity أو Fragment للتسجيل. استخدم ViewModel فقط لتخزين الحالة، وأنشئ launcher في Activity أو Fragment ومرر النتيجة إلى ViewModel.

ما هو أدنى SDK مطلوب لـ Activity Result API؟

Activity Result API متاح بدءًا من المكتبة activity-ktx 1.2.0. أدنى SDK هو API Level 14 (Android 4.0)، لكن معظم العقود تعمل فقط على API Level 19+.

ماذا يحدث إذا تم استدعاء launch مرتين قبل تلقي النتيجة؟

الاستدعاء المتكرر لـ launch قبل اكتمال العملية الأولى سيتم تجاهله. Activity Result API لا يدعم التشغيل المتوازي — انتظر callback من العملية الأولى قبل استدعاء جديد.

كيف أستبدل onActivityResult في الكود القديم؟

الترحيل يتم عن طريق استبدال استدعاء startActivityForResult بـ registerForActivityResult مع Contract المناسب. قم بإزالة onActivityResult وعالج النتيجة في callback الخاص بـ launcher. توفر Google دليل ترحيل في وثائق Android Developers.

هل يعمل ActivityResultLauncher مع مكتبات مثل ML Kit أو Barcode Scanner؟

نعم، العديد من المكتبات تدعم التكامل عبر ActivityResultContracts. على سبيل المثال، يستخدم ML Kit Barcode Scanner StartIntentSenderForResult لتشغيل الماسح الضوئي. تحقق من وثائق المكتبة المحددة.

الخلاصة

  • ActivityResultLauncher — API حديث آمن من حيث الأنواع يحل محل startActivityForResult القديم
  • Contract يحدد أنواع بيانات الإدخال والإخراج، مما يلغي المطابقة اليدوية لـ requestCode
  • التسجيل يتم في مرحلة التهيئة، ويتم تسليم النتيجة مضمونة عبر callback
  • العقود المدمجة تغطي الكاميرا، المعرض، الأذونات، المستندات وجهات الاتصال
  • Jetpack Compose يستخدم rememberLauncherForActivityResult للعمل مع API
  • Contracts المخصصة تسمح بإعادة استخدام منطق التشغيل بين المكونات
  • API يحافظ على الحالة أثناء تغييرات التكوين عبر SavedStateRegistry

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

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

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

اقرأ أيضًا