Nhận kết quả từ một hoạt động

Việc bắt đầu một hoạt động khác, dù là hoạt động trong ứng dụng của bạn hay từ một ứng dụng khác, không cần phải là thao tác một chiều. Bạn cũng có thể bắt đầu một hoạt động khác và nhận lại kết quả. Ví dụ: Ứng dụng của bạn có thể khởi động một ứng dụng máy ảnh và sau đó nhận ảnh đã chụp. Hoặc bạn có thể khởi động ứng dụng Danh bạ để người dùng chọn một địa chỉ liên hệ, nhờ đó bạn sẽ nhận được thông tin liên hệ.

Mặc dù API startActivityForResult()onActivityResult() cơ sở có sẵn trên lớp Activity thuộc tất cả cấp độ API, nhưng bạn đặc biệt nên sử dụng Activity Result API được giới thiệu trong Activity (Hoạt động) và Fragment (Mảnh) AndroidX.

Activity Result API cung cấp các thành phần để đăng ký một kết quả, khởi động kết quả và xử lý kết quả sau khi hệ thống gửi đi.

Đăng ký lệnh gọi lại cho Kết quả Activity (Hoạt động)

Khi bắt đầu một hoạt động cho một kết quả, có thể (và gần như chắc chắn trong trường hợp các hoạt động cần nhiều bộ nhớ như sử dụng máy ảnh) quá trình và hoạt động của bạn sẽ bị huỷ bỏ do bộ nhớ sắp hết.

Vì lý do này, Activity Result API tách biệt lệnh gọi lại kết quả từ vị trí trong mã nơi bạn chạy hoạt động khác. Vì lệnh gọi lại kết quả cần có sẵn khi quy trình và hoạt động của bạn được tạo lại, nên lệnh gọi lại phải được đăng ký vô điều kiện mỗi khi bạn tạo hoạt động, ngay cả khi logic chỉ chạy hoạt động khác đã xảy ra dựa trên hoạt động đầu vào của người dùng hoặc logic kinh doanh khác.

Khi ở trong ComponentActivity hoặc Fragment, API Activity Result sẽ cung cấp một API registerForActivityResult() để đăng ký lệnh gọi lại kết quả. registerForActivityResult() lấy ActivityResultContractActivityResultCallback và trả về một ActivityResultLauncher mà bạn sẽ sử dụng để chạy hoạt động khác.

ActivityResultContract xác định loại đầu vào cần thiết để tạo ra kết quả cùng với loại đầu ra của kết quả. API cung cấp các hợp đồng mặc định cho các thao tác theo ý định cơ bản như chụp ảnh, yêu cầu quyền, v.v. Bạn cũng có thể tạo các hợp đồng tuỳ chỉnh của riêng mình.

ActivityResultCallback là giao diện phương thức duy nhất có phương thức onActivityResult() lấy đối tượng của loại đầu ra được xác định trong ActivityResultContract:

Kotlin

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

Java

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

Nếu có nhiều lệnh gọi kết quả hoạt động sử dụng nhiều hợp đồng khác nhau hoặc muốn có các lệnh gọi lại riêng biệt, thì bạn có thể gọi registerForActivityResult() nhiều lần để đăng ký nhiều thực thể ActivityResultLauncher. Bạn phải luôn gọi registerForActivityResult() theo cùng một thứ tự cho mỗi lượt tạo mảnh hoặc hoạt động để đảm bảo kết quả đang truyền được gửi tới đúng lệnh gọi lại.

Bạn có thể gọi lệnh registerForActivityResult() một cách an toàn trước khi tạo mảnh hoặc hoạt động, nhờ vậy có thể sử dụng trực tiếp lệnh đó khi khai báo các biến thành viên cho các thực thể ActivityResultLauncher đã trả về.

Khởi động một hoạt động cho kết quả

Dù đăng ký lệnh gọi lại của bạn nhưng registerForActivityResult() không chạy hoạt động khác và bắt đầu yêu cầu kết quả. Thay vào đó, đây là trách nhiệm của thực thể ActivityResultLauncher được trả về.

Nếu có giá trị đầu vào, trình chạy sẽ lấy giá trị đầu vào khớp với loại của ActivityResultContract. Hoạt động gọi launch() sẽ bắt đầu quy trình tạo kết quả. Khi người dùng thực hiện xong hoạt động sau đó và trả về, thì onActivityResult() từ ActivityResultCallback sẽ được thực thi, như trong ví dụ sau:

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'd like to allow the user to 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 savedInstanceState: Bundle) {
    // ...

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

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

Phiên bản nạp chồng của launch() cho phép bạn truyền ActivityOptionsCompat ngoài giá trị đầu vào.

Nhận kết quả hoạt động trong một lớp riêng

Mặc dù lớp ComponentActivityFragment triển khai giao diện ActivityResultCaller để cho phép bạn sử dụng API registerForActivityResult(), nhưng bạn cũng có thể nhận kết quả hoạt động trong một lớp riêng biệt không triển khai ActivityResultCaller bằng cách sử dụng trực tiếp ActivityResultRegistry.

Ví dụ: Bạn có thể triển khai LifecycleObserver giúp xử lý việc đăng ký hợp đồng cùng với việc khởi động trình chạy:

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

Khi sử dụng API ActivityResultRegistry, bạn đặc biệt nên sử dụng API lấy LifecycleOwner, vì LifecycleOwner sẽ tự động xoá trình chạy đã đăng ký của bạn khi Lifecycle bị huỷ. Tuy nhiên, trong trường hợp không có LifecycleOwner, mỗi lớp ActivityResultLauncher sẽ cho phép bạn gọi unregister() theo cách thủ công làm phương thức thay thế.

Kiểm thử

Theo mặc định, registerForActivityResult() tự động sử dụng ActivityResultRegistry do hoạt động cung cấp. API này cũng cung cấp phương thức nạp chồng cho phép bạn truyền thực thể ActivityResultRegistry riêng có thể dùng để kiểm thử các lệnh gọi kết quả hoạt động mà không thực sự chạy hoạt động khác.

Khi Kiểm thử các mảnh của ứng dụng, bạn có thể cung cấp ActivityResultRegistry kiểm thử bằng cách sử dụng FragmentFactory để truyền ActivityResultRegistry đến hàm khởi tạo của mảnh.

Ví dụ: Một mảnh sử dụng hợp đồng TakePicturePreview để tải hình thu nhỏ của hình ảnh có thể được ghi tương tự như sau:

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

    // ...
}

Khi tạo một ActivityResultRegistry kiểm thử cụ thể, bạn phải triển khai phương thức onLaunch(). Thay vì gọi startActivityForResult(), phương thức triển khai kiểm thử có thể gọi trực tiếp dispatchResult(), cung cấp kết quả chính xác mà bạn muốn sử dụng trong kiểm thử của mình:

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

Việc kiểm thử hoàn chỉnh sẽ tạo ra kết quả dự kiến, tạo một ActivityResultRegistry kiểm thử, truyền thông tin vào mảnh, kích hoạt trình chạy (trực tiếp hoặc thông qua API kiểm thử khác như Espresso), rồi xác minh kết quả:

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

Tạo hợp đồng tuỳ chỉnh

Mặc dù ActivityResultContracts chứa một số lớp ActivityResultContract được tạo sẵn để sử dụng, nhưng bạn có thể cung cấp hợp đồng riêng mang đến API an toàn loại chính xác mà bạn yêu cầu.

Mỗi ActivityResultContract yêu cầu xác định các lớp đầu vào và đầu ra, sử dụng Void (trong Kotlin, sử dụng Void? hoặc Unit) làm loại đầu vào nếu bạn không yêu cầu bất kỳ đầu vào nào.

Mỗi hợp đồng phải triển khai phương thức createIntent(). Phương thức này sẽ lấy Context và giá trị nhập cũng như tạo Intent sẽ được sử dụng với startActivityForResult().

Mỗi hợp đồng cũng phải triển khai parseResult(), tạo đầu ra từ resultCode nhất định (ví dụ: Activity.RESULT_OK hoặc Activity.RESULT_CANCELED) và Intent.

Các hợp đồng có thể tuỳ ý triển khai getSynchronousResult() nếu có thể xác định kết quả cho một giá trị đầu vào nhất định mà không cần gọi createIntent(), bắt đầu hoạt động khác và sử dụng parseResult() để tạo kết quả.

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

Nếu không cần hợp đồng tuỳ chỉnh, bạn có thể sử dụng hợp đồng StartActivityForResult. Đây là một hợp đồng chung, lấy bất kỳ Intent nào làm giá trị đầu vào và trả về ActivityResult, cho phép bạn trích xuất resultCodeIntent như một phần của lệnh gọi lại, như trong ví dụ sau:

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