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

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

در حالی که APIهای زیربنایی startActivityForResult() و onActivityResult() در کلاس Activity در تمام سطوح API موجود هستند، Google قویاً استفاده از APIهای Activity Result معرفی شده در کلاس‌های Activity و Fragment AndroidX را توصیه می‌کند.

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

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

هنگام شروع یک فعالیت برای یک نتیجه، ممکن است - و در موارد عملیات فشرده حافظه مانند استفاده از دوربین، تقریباً مطمئن است - که فرآیند شما و فعالیت شما به دلیل حافظه کم از بین برود.

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

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

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

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() را به همان ترتیب برای هر ایجاد فرگمنت یا اکتیویتی خود فراخوانی کنید تا نتایج inflight به callback صحیح تحویل داده شود.

registerForActivityResult() برای فراخوانی قبل از ایجاد قطعه یا اکتیویتی شما بی خطر است، و اجازه می دهد که مستقیماً هنگام اعلام متغیرهای عضو برای نمونه های ActivityResultLauncher برگشتی استفاده شود.

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

در حالی که registerForActivityResult() callback شما را ثبت می‌کند، فعالیت دیگر را راه‌اندازی نمی‌کند و درخواست برای نتیجه را آغاز نمی‌کند. در عوض، این مسئولیت بر عهده نمونه بازگشتی 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 را پیاده سازی می کنند تا به شما امکان استفاده از API های 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();
            }
        });
    }
}

هنگام استفاده از APIهای ActivityResultRegistry ، Google قویاً توصیه می‌کند از APIهایی استفاده کنید که 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 آزمایشی می‌سازد، آن را به قطعه ارسال می‌کند، راه‌انداز را مستقیماً یا با استفاده از سایر APIهای آزمایشی مانند 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 از پیش ساخته شده برای استفاده است، شما می توانید قراردادهای خود را ارائه دهید که API دقیق و ایمن مورد نیاز خود را ارائه می دهد.

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

کاتلین

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