دریافت نتیجه از فعالیت

شروع کردن فعالیتی دیگر، چه در برنامه شما باشد چه در برنامه‌ای دیگر، لازم نیست عملیاتی یک‌طرفه باشد. همچنین می‌توانید فعالیتی را شروع کنید و نتیجه را دریافت کنید. برای مثال، برنامه‌تان می‌تواند برنامه دوربین را راه‌اندازی کند و عکس گرفته‌شده را به‌عنوان نتیجه دریافت کند. یا ممکن است برنامه «مخاطبین» را راه‌اندازی کنید تا کاربر مخاطبی را انتخاب کند و سپس جزئیات مخاطب را به‌عنوان نتیجه دریافت کنید.

While the underlying startActivityForResult() and onActivityResult() APIs are available on the Activity class on all API levels, Google strongly recommends using the Activity Result APIs introduced in AndroidX Activity and Fragment classes.

Activity Result API مؤلفه‌هایی برای ثبت نتیجه، راه‌اندازی فعالیتی که نتیجه را تولید می‌کند، و مدیریت نتیجه پس‌از ارسال آن توسط سیستم ارائه می‌دهد.

ثبت کردن پاسخ تماس برای نتیجه فعالیت

هنگام شروع فعالیتی برای نتیجه، ممکن است (و در موارد عملیات‌های با مصرف حافظه بالا مانند استفاده از دوربین، تقریباً قطعی است) که فرایند و فعالیت شما به‌دلیل کمبود حافظه ازبین برود.

به همین دلیل، Activity Result APIها فراخوانی برگشت نتیجه را از جایی در کد شما که فعالیت دیگر را راه‌اندازی می‌کنید، جدا می‌کنند. ازآنجایی‌که فراخوان برگشتی نتیجه باید هنگام بازآفرینی فرایند و فعالیت شما دردسترس باشد، فراخوان برگشتی باید هربار که فعالیت شما ایجاد می‌شود بدون قیدوشرط ثبت شود، حتی اگر منطق راه‌اندازی فعالیت دیگر فقط براساس ورودی کاربر یا منطق کسب‌وکار دیگری اتفاق بیفتد.

وقتی در ComponentActivity یا Fragment هستید، میاناهای برنامه‌سازی کاربردی Activity Result یک میانای برنامه‌سازی کاربردی registerForActivityResult() برای ثبت فراخوانی برگشتی نتیجه ارائه می‌دهند. registerForActivityResult() ActivityResultContract و ActivityResultCallback را می‌گیرد و ActivityResultLauncher را برمی‌گرداند که از آن برای راه‌اندازی فعالیت دیگر استفاده می‌کنید.

ActivityResultContract نوع ورودی موردنیاز برای تولید نتیجه را به‌همراه نوع خروجی نتیجه تعریف می‌کند. میاناهای برنامه‌سازی کاربردی قراردادهای پیش‌فرضی برای کنش‌های هدف پایه مثل عکس گرفتن، درخواست اجازه‌ها، و غیره ارائه می‌دهند. همچنین می‌توانید قرارداد سفارشی ایجاد کنید.

‫ActivityResultCallback یک میانای تک‌روشی با یک onActivityResult() روش است که شیئی از نوع خروجی تعریف‌شده در ActivityResultContract را می‌گیرد:

کاتلین

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

جاوا

// 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 اجرا می‌شود، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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/*")
    }
}

جاوا

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 را پیاده‌سازی کنید که ثبت قرارداد را همراه با راه‌اندازی راه‌انداز مدیریت می‌کند:

کاتلین

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()
        }
    }
}

جاوا

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 برای دریافت ریزعکس تصویر استفاده می‌کند ممکن است مشابه کد زیر نوشته شود:

کاتلین

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

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

    // ...
}

جاوا

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 را نشان می‌دهد:

کاتلین

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)
    }
}

جاوا

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 را به‌عنوان بخشی از فراخوانی برگشتی خود استخراج کنید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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))
    }
}

جاوا

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));
        }
    });
}