ActivityResultLauncher هو مكون من Android Activity Result API، تم تقديمه في الإصدار Activity 1.2.0 من مكتبة androidx.activity. يحل محل الطريقتين القديمتين startActivityForResult و onActivityResult، اللتين كانتا جزءًا من Android SDK منذ إنشائه. وفقًا لـ Android Developers (2024)، فإن API الجديد يزيل مشاكل الارتباط القوي مع Activity وغياب أمان الأنواع. ActivityResultLauncher يتم تسجيله مسبقًا ويستخدم Contract لكتابة صارمة لأنواع البيانات المدخلة والمخرجة.
النقاط الرئيسية
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.
startActivityForResult كان جزءًا من Android SDK منذ API Level 1 (2008) وبقي الطريقة الرئيسية للحصول على نتيجة من Activity لأكثر من 12 عامًا. ومع ذلك، كانت لهذه الطريقة عيوب أساسية عالجتها Google في Activity Result API. دعنا نلقي نظرة على المشكلات الرئيسية وكيف يحلها API الجديد.
طريقة startActivityForResult مرتبطة بـ Activity و Fragment عبر requestCode — رقم صحيح عشوائي يُمرر إلى onActivityResult. كان المطور يطابق الكود يدويًا مع العملية المشغلة، مما يؤدي إلى أخطاء في إعادة استخدام الكود والوراثة. ActivityResultLauncher يلغي requestCode تمامًا: يتم ربط callback مع launcher محدد في وقت التسجيل ويتم استدعاؤه فقط له.
أثناء تغييرات التكوين (تدوير الشاشة، تغيير اللغة)، يتم إعادة إنشاء Activity وقد لا يتم استدعاء onActivityResult — يتم فقدان callback. Activity Result API يحفظ ويستعيد حالة launcher تلقائيًا عبر SavedStateRegistry، مما يضمن استلام النتيجة حتى بعد إعادة إنشاء Activity.
API القديم يمرر النتيجة عبر Intent مع Bundle، حيث لا يتم التحقق من المفاتيح وأنواع البيانات بواسطة المترجم. Activity Result API يستخدم Contract — واجهة عامة تحدد نوع بيانات الإدخال (I) ونوع النتيجة (O). يتم اكتشاف أخطاء عدم تطابق الأنواع في وقت الترجمة، وليس في وقت التشغيل.
| الخاصية | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | إدارة يدوية مطلوبة | تلقائي، غير مطلوب |
| أمان الأنواع | لا | Contract عام |
| الحفظ عند التدوير | يُفقد | SavedStateRegistry |
| أدنى API | API Level 1 | Activity 1.2.0 |
| الاستخدام في Compose | غير مدعوم | rememberLauncherForActivityResult |
Contract هو واجهة ActivityResultContract<I, O>، التي تحدد كيفية تشغيل Activity وكيفية تفسير النتيجة. توفر Google مجموعة من العقود المدمجة للسيناريوهات النموذجية التي تغطي معظم احتياجات المطور.
StartIntentSenderForResult — عقد أساسي لتشغيل IntentSender. يُستخدم في السيناريوهات النظامية، على سبيل المثال عند التفويض عبر Google Sign-In أو الدفع عبر Google Pay. معلمة الإدخال هي PendingIntent، والإخراج هو ActivityResult مع الكود و Intent.
RequestMultiplePermissions — عقد لطلب أذونات متعددة في وقت واحد على Android 6.0+. معلمة الإدخال هي مصفوفة String بأسماء الأذونات، والإخراج هو Map<String, Boolean> بنتيجة كل طلب. سابقًا كان هذا يتطلب تحليلًا يدويًا في onRequestPermissionsResult مع مطابقة رموز الطلبات.
TakePicture — عقد لالتقاط صورة عبر كاميرا النظام. الإدخال هو Uri لحفظ الصورة، والإخراج هو Boolean (نجاح). TakeVideo يعمل بالمثل مع الفيديو. تحل هذه العقود محل MediaStore.ACTION_IMAGE_CAPTURE القديم بسلوك غير مستقر على الأجهزة المختلفة.
GetContent — عقد لاختيار المحتوى عبر منتقي النظام. الإدخال هو نوع MIME (مثل image/*)، والإخراج هو Uri للملف المحدد. OpenDocument يختلف بدعمه للتحديد المتعدد والتصفية حسب أنواع المستندات. يعمل كلا العقدين عبر SAF (Storage Access Framework).
CreateDocument — عقد لإنشاء مستند جديد عبر حوار النظام. يختار المستخدم اسمًا ومجلدًا، ويعيد النظام Uri للكتابة. OpenDocumentTree يوفر الوصول إلى دليل كامل — يختار المستخدم مجلدًا، ويتلقى التطبيق tree-uri لقراءة وكتابة جميع الملفات داخله.
النمط الأساسي لاستخدام ActivityResultLauncher في Android التقليدي يتكون من خطوتين: التسجيل عبر registerForActivityResult في مرحلة التهيئة واستدعاء launch استجابةً لإجراء المستخدم. دعنا نلقي نظرة على مثال نموذجي لاختيار صورة من المعرض.
سجل launcher في onCreate الخاص بـ Activity — هذا يضمن أن callback جاهز قبل أي استدعاء محتمل. لا تسجل launcher قبل التشغيل مباشرة — هذا ينتهك عقد API ويمكن أن يؤدي إلى فقدان النتيجة عند إعادة إنشاء Activity.
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، يتم التسجيل في onCreate أو onAttach أو التهيئة في onCreateView. FragmentActivity يمرر launcher عبر Activity الأصلية، لذلك تتم معالجة النتيجة داخل Fragment، وليس في Activity. هذا يحسن التغليف مقارنة بـ onActivityResult، حيث كانت جميع النتائج من جميع Fragments تُجمع في طريقة واحدة في Activity.
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose يوفر دالة composable خاصة لـ Activity Result API — rememberLauncherForActivityResult. على عكس النهج التقليدي، في Compose يتم إنشاء launcher ككائن مرتبط بدورة حياة composable عبر remember. هذا يسمح باستخدام Activity Result API بالكامل في أسلوب تصريحي دون وصول مباشر إلى Activity أو Fragment.
rememberLauncherForActivityResult يأخذ Contract و callback، ويعيد ActivityResultLauncher. يتم الحفاظ على launcher أثناء إعادة التركيب ويتم تنظيفه تلقائيًا عند الخروج من التركيب. يحدث استدعاء launch استجابة لحدث — على سبيل المثال، نقرة زر أو تغيير حالة.
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("اختيار صورة")
}
}
طلبات الأذونات في Compose تتم أيضًا عبر rememberLauncherForActivityResult مع عقد RequestPermission أو RequestMultiplePermissions. توصي Google باستخدام accompanist-permissions، لكنه داخليًا يستخدم أيضًا Activity Result API. للتحكم في حالة الأذونات، من المناسب تخزين الحالة في remember أو ViewModel.
Activity Result API أزال العديد من مشكلات النهج القديم، لكن الاستخدام غير الصحيح يمكن أن يؤدي إلى أنواع جديدة من الأخطاء. دعنا نلقي نظرة على المشكلات الأكثر شيوعًا وكيفية تجنبها.
تسجيل launcher يجب أن يتم أثناء تهيئة المكون — في onCreate لـ Activity أو مُهيئ Fragment. إذا سجلت launcher داخل lambda أو callback أو coroutine، عند إعادة إنشاء Activity قد يتم التسجيل مرة أخرى ويفقد launcher القديم الاتصال بالنتيجة.
كل launcher يحصل على مفتاح فريد لحفظ الحالة. إذا سجلت اثنين من launchers بنفس Contract في مكون واحد، قد يقوم SavedStateRegistry باستبدال حالة أحدهما بالآخر. يحذر Android Studio من هذا عبر قاعدة lint UnnecessaryRegisterForActivityResult، لكن من الأفضل التحكم في التفرد يدويًا.
قد يقوم المستخدم بإلغاء الإجراء — الضغط على زر الرجوع النظامي، تصغير التطبيق، أو التبديل إلى تطبيق آخر. في هذه الحالة، سيتلقى callback قيمة null أو ActivityResult مع RESULT_CANCELED. تحقق دائمًا من أن النتيجة ليست null قبل استخدامها لتجنب NullPointerException.
إذا كان تطبيقك يشغل بشكل متكرر سيناريوهات متشابهة — على سبيل المثال، اختيار جهة اتصال وإرجاع الاسم والهاتف — قم بإنشاء Contract مخصص. هذا يحسن قابلية قراءة الكود ويسمح بتغييرات مركزية في منطق التشغيل ومعالجة النتائج.
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 يتطلب سياق Activity أو Fragment للتسجيل. استخدم ViewModel فقط لتخزين الحالة، وأنشئ launcher في Activity أو Fragment ومرر النتيجة إلى ViewModel.
Activity Result API متاح بدءًا من المكتبة activity-ktx 1.2.0. أدنى SDK هو API Level 14 (Android 4.0)، لكن معظم العقود تعمل فقط على API Level 19+.
الاستدعاء المتكرر لـ launch قبل اكتمال العملية الأولى سيتم تجاهله. Activity Result API لا يدعم التشغيل المتوازي — انتظر callback من العملية الأولى قبل استدعاء جديد.
الترحيل يتم عن طريق استبدال استدعاء startActivityForResult بـ registerForActivityResult مع Contract المناسب. قم بإزالة onActivityResult وعالج النتيجة في callback الخاص بـ launcher. توفر Google دليل ترحيل في وثائق Android Developers.
نعم، العديد من المكتبات تدعم التكامل عبر ActivityResultContracts. على سبيل المثال، يستخدم ML Kit Barcode Scanner StartIntentSenderForResult لتشغيل الماسح الضوئي. تحقق من وثائق المكتبة المحددة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا