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 پردازش میشدند: راهاندازی دوربین، گالری، درخواست مخاطبین، مجوزها و Activityهای سفارشی. علاوه بر این، API قابل گسترش است: توسعهدهنده میتواند Contractهای سفارشی برای سناریوهای خاص تبادل داده بین Activityها ایجاد کند.
startActivityForResult از نسخه API Level 1 (2008) بخشی از Android SDK بود و بیش از 12 سال روش اصلی دریافت نتیجه از Activity باقی ماند. با این حال، این روش دارای نقصهای اساسی بود که Google در Activity Result API برطرف کرد. بیایید مشکلات اصلی و نحوه حل آنها توسط API جدید را بررسی کنیم.
متد startActivityForResult از طریق requestCode به Activity و Fragment متصل است — یک عدد صحیح دلخواه که به onActivityResult منتقل میشود. توسعهدهنده به صورت دستی کد را با عملیات راهاندازی شده مطابقت میداد که منجر به خطا در استفاده مجدد از کد و وراثت میشد. ActivityResultLauncher requestCode را کاملاً حذف میکند: callback در مرحله ثبت به launcher خاصی متصل میشود و فقط برای آن فراخوانی میشود.
در تغییرات پیکربندی (چرخش صفحه، تغییر زبان) Activity بازسازی میشد و onActivityResult ممکن بود فعال نشود — callback از بین میرفت. Activity Result API به طور خودکار وضعیت launcher را از طریق SavedStateRegistry ذخیره و بازیابی میکند که دریافت نتیجه را حتی پس از بازسازی Activity تضمین میکند.
API قدیمی نتیجه را از طریق Intent با Bundle منتقل میکرد که در آن کلیدها و نوع دادهها توسط کامپایلر بررسی نمیشدند. Activity Result API از Contract استفاده میکند — یک رابط generic که نوع داده ورودی (I) و نوع نتیجه (O) را تعیین میکند. خطاهای عدم تطابق نوع در مرحله کامپایل شناسایی میشوند، نه در زمان اجرا.
| ویژگی | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | نیاز به مدیریت دستی دارد | خودکار، نیاز نیست |
| امنیت نوع | ندارد | Generic 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 بهبود میبخشد، جایی که همه نتایج از همه Fragmentها در یک متد 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 را در داخل لامبدا، callback یا کوروتین ثبت کنید، هنگام بازسازی Activity ممکن است ثبت دوباره انجام شود و launcher قدیمی ارتباط خود را با نتیجه از دست بدهد.
هر launcher یک کلید منحصر به فرد برای ذخیره وضعیت دریافت میکند. اگر دو launcher با 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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید