الحصول على نتيجة من نشاط

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

على الرغم من توفُّر واجهات برمجة التطبيقات startActivityForResult() وonActivityResult() الأساسية في صف Activity على جميع مستويات واجهة برمجة التطبيقات، تنصح Google بشدّة باستخدام واجهات برمجة تطبيقات نتائج الأنشطة التي تم تقديمها في صفَّي AndroidX Activity وFragment.

توفر واجهات برمجة التطبيقات لنتائج الأنشطة مكونات للتسجيل للنتيجة وإطلاق النتيجة والتعامل معها بمجرد إرسالها بواسطة النظام.

تسجيل معاودة الاتصال لنتيجة نشاط

عند بدء نشاط ما من نتيجة، من الممكن - وفي حالات العمليات التي تستهلك قدرًا كبيرًا من الذاكرة مثل استخدام الكاميرا - من المؤكد تقريبًا أن يتم إتلاف عمليتك ونشاطك بسبب انخفاض الذاكرة.

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

عند استخدام واجهة ComponentActivity أو Fragment، توفّر واجهات برمجة التطبيقات لنتائج النشاط registerForActivityResult() واجهة برمجة تطبيقات لتسجيل طلب معاودة الاتصال بالنتيجة. تأخذ registerForActivityResult() علامة ActivityResultContract وActivityResultCallback وتعرض علامة ActivityResultLauncher، التي تستخدمها لتشغيل النشاط الآخر.

تحدد ActivityResultContract نوع الإدخال المطلوب للحصول على نتيجة إلى جانب نوع الإخراج للنتيجة. توفّر واجهات برمجة التطبيقات العقود التلقائية لالإجراءات الأساسية المستندة إلى الأهداف، مثل التقاط صورة وطلب الأذونات وما إلى ذلك. يمكنك أيضًا إنشاء عقد مخصّص.

ActivityResultCallback هي واجهة ذات طريقة أحادية تتضمّن الطريقة onActivityResult() التي تأخذ كائنًا من نوع الإخراج المحدّد في ActivityResultContract:

Kotlin

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Java

// GetContent creates an ActivityResultLauncher<String> to let you pass
// in the mime type you want to let the user select
ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(),
    new ActivityResultCallback<Uri>() {
        @Override
        public void onActivityResult(Uri uri) {
            // Handle the returned Uri
        }
});

إذا كان لديك استدعاءات متعددة لنتائج النشاط وكنت إما تستخدم عقودًا مختلفة أو تريد معاودة اتصال منفصلة، يمكنك استدعاء "registerForActivityResult()" عدة مرات لتسجيل حالات ActivityResultLauncher متعددة. عليك طلب الرمز registerForActivityResult() بنفس الترتيب لكل عملية إنشاء للجزء أو النشاط حتى يتم تسليم نتائج البحث أثناء الطيران إلى معاودة الاتصال الصحيحة.

من الآمن الاتصال بـ "registerForActivityResult()" قبل إنشاء الجزء أو النشاط، ما يسمح باستخدامه مباشرةً عند تعريف متغيّرات الأعضاء في مثيلات ActivityResultLauncher التي يتم عرضها.

تشغيل نشاط للنتائج

أثناء تسجيل registerForActivityResult() لمعاودة الاتصال، لا يطلق النشاط الآخر ويبدأ الطلب للحصول على نتيجة. بدلاً من ذلك، هذه مسؤولية مثيل ActivityResultLauncher الذي تم عرضه.

في حال توفُّر إدخال، سيحصل مشغّل التطبيقات على الإدخال الذي يتطابق مع نوع ActivityResultContract. يؤدي استدعاء launch() إلى بدء عملية الحصول على النتيجة. عندما ينتهي المستخدم من النشاط التالي ثم يعود، يتم تنفيذ onActivityResult() من ActivityResultCallback، كما هو موضّح في المثال التالي:

Kotlin

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you want to let the user select
        // as the input
        getContent.launch("image/*")
    }
}

Java

ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(),
    new ActivityResultCallback<Uri>() {
        @Override
        public void onActivityResult(Uri uri) {
            // Handle the returned Uri
        }
});

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // ...

    Button selectButton = findViewById(R.id.select_button);

    selectButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // Pass in the mime type you want to let the user select
            // as the input
            mGetContent.launch("image/*");
        }
    });
}

يتيح لك إصدار launch() ذي التحميل الزائد تمرير ActivityOptionsCompat بالإضافة إلى الإدخال.

تلقّي نتيجة نشاط في صف منفصل

على الرغم من أنّ الصفَّين ComponentActivity وFragment يستخدمان واجهة ActivityResultCaller للسماح لك باستخدام واجهات برمجة تطبيقات registerForActivityResult()، يمكنك أيضًا تلقّي نتيجة النشاط في صف منفصل لا ينفِّذ ActivityResultCaller باستخدام ActivityResultRegistry مباشرةً.

على سبيل المثال، قد ترغب في تنفيذ LifecycleObserver الذي يعالج تسجيل عقد إلى جانب تشغيل مشغّل التطبيقات:

Kotlin

class MyLifecycleObserver(private val registry : ActivityResultRegistry)
        : DefaultLifecycleObserver {
    lateinit var getContent : ActivityResultLauncher<String>

    override fun onCreate(owner: LifecycleOwner) {
        getContent = registry.register("key", owner, GetContent()) { uri ->
            // Handle the returned Uri
        }
    }

    fun selectImage() {
        getContent.launch("image/*")
    }
}

class MyFragment : Fragment() {
    lateinit var observer : MyLifecycleObserver

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
        lifecycle.addObserver(observer)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val selectButton = view.findViewById<Button>(R.id.select_button)

        selectButton.setOnClickListener {
            // Open the activity to select an image
            observer.selectImage()
        }
    }
}

Java

class MyLifecycleObserver implements DefaultLifecycleObserver {
    private final ActivityResultRegistry mRegistry;
    private ActivityResultLauncher<String> mGetContent;

    MyLifecycleObserver(@NonNull ActivityResultRegistry registry) {
        mRegistry = registry;
    }

    public void onCreate(@NonNull LifecycleOwner owner) {
        // ...

        mGetContent = mRegistry.register(“key”, owner, new GetContent(),
            new ActivityResultCallback<Uri>() {
                @Override
                public void onActivityResult(Uri uri) {
                    // Handle the returned Uri
                }
            });
    }

    public void selectImage() {
        // Open the activity to select an image
        mGetContent.launch("image/*");
    }
}

class MyFragment extends Fragment {
    private MyLifecycleObserver mObserver;

    @Override
    void onCreate(Bundle savedInstanceState) {
        // ...

        mObserver = new MyLifecycleObserver(requireActivity().getActivityResultRegistry());
        getLifecycle().addObserver(mObserver);
    }

    @Override
    void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Button selectButton = findViewById(R.id.select_button);
        selectButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mObserver.selectImage();
            }
        });
    }
}

عند استخدام واجهات برمجة تطبيقات ActivityResultRegistry، تنصح Google بشدة باستخدام واجهات برمجة التطبيقات التي تأخذ LifecycleOwner، لأنّ LifecycleOwner تزيل المشغّل المسجّل تلقائيًا عند تدمير Lifecycle. مع ذلك، في الحالات التي لا تتوفّر فيها السمة LifecycleOwner، تتيح لك كل صف في ActivityResultLauncher استدعاء unregister() يدويًا كبديل.

الاختبار

بشكل تلقائي، يستخدم "registerForActivityResult()" تلقائيًا سمة ActivityResultRegistry التي يوفّرها النشاط. وتوفر لك هذه الطريقة أيضًا حملاً زائدًا يتيح لك تمرير مثيل ActivityResultRegistry الخاص بك والذي يمكنك استخدامه لاختبار استدعاءات نتائج النشاط بدون بدء أي نشاط آخر.

عند اختبار أجزاء تطبيقك، يمكنك تقديم اختبار ActivityResultRegistry باستخدام FragmentFactory لتمرير ActivityResultRegistry إلى الدالة الإنشائية للجزء.

على سبيل المثال، يمكن أن تتم كتابة الجزء الذي يستخدم عقد TakePicturePreview للحصول على صورة مصغّرة للصورة على النحو التالي:

Kotlin

class MyFragment(
    private val registry: ActivityResultRegistry
) : Fragment() {
    val thumbnailLiveData = MutableLiveData<Bitmap?>

    val takePicture = registerForActivityResult(TakePicturePreview(), registry) {
        bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap)
    }

    // ...
}

Java

public class MyFragment extends Fragment {
    private final ActivityResultRegistry mRegistry;
    private final MutableLiveData<Bitmap> mThumbnailLiveData = new MutableLiveData();
    private final ActivityResultLauncher<Void> mTakePicture =
        registerForActivityResult(new TakePicturePreview(), mRegistry, new ActivityResultCallback<Bitmap>() {
            @Override
            public void onActivityResult(Bitmap thumbnail) {
                mThumbnailLiveData.setValue(thumbnail);
            }
        });

    public MyFragment(@NonNull ActivityResultRegistry registry) {
        super();
        mRegistry = registry;
    }

    @VisibleForTesting
    @NonNull
    ActivityResultLauncher<Void> getTakePicture() {
        return mTakePicture;
    }

    @VisibleForTesting
    @NonNull
    LiveData<Bitmap> getThumbnailLiveData() {
        return mThumbnailLiveData;
    }

    // ...
}

عند إنشاء ActivityResultRegistry خاص بالاختبار، يجب تنفيذ طريقة onLaunch(). بدلاً من طلب الرقم startActivityForResult()، يمكن لتنفيذ الاختبار الاتصال بـ dispatchResult() مباشرة، لتقديم النتائج الدقيقة التي تريد استخدامها في الاختبار:

val testRegistry = object : ActivityResultRegistry() {
    override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
    ) {
        dispatchResult(requestCode, expectedResult)
    }
}

ينشئ الاختبار الكامل النتيجة المتوقعة، وينشئ اختبارًا ActivityResultRegistry، ويمرره إلى الجزء، ويشغِّل مشغِّل التطبيقات إما بشكلٍ مباشر أو باستخدام واجهات برمجة تطبيقات اختبارية أخرى، مثل Espresso، ثم يتحقّق من النتائج:

@Test
fun activityResultTest {
    // Create an expected result Bitmap
    val expectedResult = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16)

    // Create the test ActivityResultRegistry
    val testRegistry = object : ActivityResultRegistry() {
            override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
        ) {
            dispatchResult(requestCode, expectedResult)
        }
    }

    // Use the launchFragmentInContainer method that takes a
    // lambda to construct the Fragment with the testRegistry
    with(launchFragmentInContainer { MyFragment(testRegistry) }) {
            onFragment { fragment ->
                // Trigger the ActivityResultLauncher
                fragment.takePicture()
                // Verify the result is set
                assertThat(fragment.thumbnailLiveData.value)
                        .isSameInstanceAs(expectedResult)
            }
    }
}

إنشاء عقد مخصّص

على الرغم من أنّ ActivityResultContracts يحتوي على عدد من فئات ActivityResultContract المصمّمة مسبقًا للاستخدام، يمكنك تقديم عقودك الخاصة التي توفّر واجهة برمجة التطبيقات المناسبة للنوع والتوافق مع معايير الكتابة التي تحتاجها.

يتطلب كل ActivityResultContract فئات إدخال وإخراج محددة وتستخدم Void كنوع إدخال إذا لم تكن بحاجة إلى أي إدخال (في Kotlin، يمكنك استخدام Void? أو Unit).

يجب أن ينفّذ كل عقد طريقة createIntent() التي تستخدم Context والإدخال وتنشئ Intent المستخدمة مع startActivityForResult().

يجب أيضًا في كل عقد تنفيذ السمة parseResult()، التي تنتج الناتج من resultCode المحدّد، مثل Activity.RESULT_OK أو Activity.RESULT_CANCELED وIntent.

يمكن تطبيق العقود بشكل اختياري getSynchronousResult() إذا كان من الممكن تحديد النتيجة لإدخال معيّن بدون الحاجة إلى طلب createIntent() وبدء النشاط الآخر واستخدام parseResult() لإنشاء النتيجة.

يوضّح المثال التالي كيفية إنشاء ActivityResultContract:

Kotlin

class PickRingtone : ActivityResultContract<Int, Uri?>() {
    override fun createIntent(context: Context, ringtoneType: Int) =
        Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
            putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType)
        }

    override fun parseResult(resultCode: Int, result: Intent?) : Uri? {
        if (resultCode != Activity.RESULT_OK) {
            return null
        }
        return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
    }
}

Java

public class PickRingtone extends ActivityResultContract<Integer, Uri> {
    @NonNull
    @Override
    public Intent createIntent(@NonNull Context context, @NonNull Integer ringtoneType) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType.intValue());
        return intent;
    }

    @Override
    public Uri parseResult(int resultCode, @Nullable Intent result) {
        if (resultCode != Activity.RESULT_OK || result == null) {
            return null;
        }
        return result.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
    }
}

إذا لم تكن بحاجة إلى عقد مخصّص، يمكنك استخدام العقد StartActivityForResult. هذا عقد عام يتم فيه استخدام أي Intent كإدخال ويعرض ActivityResult، ما يتيح لك استخراج resultCode وIntent كجزء من معاودة الاتصال، كما هو موضّح في المثال التالي:

Kotlin

val startForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        val intent = result.data
        // Handle the Intent
    }
}

override fun onCreate(savedInstanceState: Bundle) {
    // ...

    val startButton = findViewById(R.id.start_button)

    startButton.setOnClickListener {
        // Use the Kotlin extension in activity-ktx
        // passing it the Intent you want to start
        startForResult.launch(Intent(this, ResultProducingActivity::class.java))
    }
}

Java

ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
    @Override
    public void onActivityResult(ActivityResult result) {
        if (result.getResultCode() == Activity.RESULT_OK) {
            Intent intent = result.getData();
            // Handle the Intent
        }
    }
});

@Override
public void onCreate(@Nullable savedInstanceState: Bundle) {
    // ...

    Button startButton = findViewById(R.id.start_button);

    startButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // The launcher with the Intent you want to start
            mStartForResult.launch(new Intent(this, ResultProducingActivity.class));
        }
    });
}