ActivityResultLauncher là một thành phần của Android Activity Result API, được giới thiệu trong phiên bản Activity 1.2.0 của thư viện androidx.activity. Nó thay thế các phương thức đã lỗi thời startActivityForResult và onActivityResult, vốn là một phần của Android SDK kể từ khi thành lập. Theo Android Developers (2024), API mới loại bỏ các vấn đề về sự gắn kết chặt chẽ với Activity và thiếu an toàn kiểu dữ liệu. ActivityResultLauncher được đăng ký trước và sử dụng Contract để nhập kiểu dữ liệu đầu vào và đầu ra một cách chặt chẽ.
Những điểm chính
ActivityResultLauncher là một lớp từ gói androidx.activity.result cung cấp cơ chế an toàn về kiểu để khởi chạy Activity và nhận kết quả. Launcher được tạo thông qua phương thức registerForActivityResult, nhận hai tham số: Contract (mô tả các kiểu đầu vào và đầu ra) và ActivityResultCallback (trình xử lý kết quả). Sau khi đăng ký, launcher sẵn sàng được gọi thông qua phương thức launch.
Sự khác biệt chính so với API cũ là sự tách biệt giữa đăng ký và khởi chạy. Việc đăng ký được thực hiện ở giai đoạn khởi tạo (Activity.onCreate hoặc Fragment.onCreate), callback được liên kết với launcher một lần và được đảm bảo sẽ kích hoạt khi kết quả trở về. Điều này loại bỏ vấn đề khi onActivityResult kích hoạt theo thứ tự không mong đợi hoặc trên một Activity đã bị hủy.
ActivityResultLauncher hỗ trợ tất cả các kịch bản trước đây được xử lý qua onActivityResult: khởi chạy camera, thư viện ảnh, yêu cầu danh bạ, quyền và các Activity tùy chỉnh. Hơn nữa, API có thể mở rộng: nhà phát triển có thể tạo Contract tùy chỉnh cho các kịch bản trao đổi dữ liệu cụ thể giữa các Activity.
startActivityForResult đã là một phần của Android SDK từ API Level 1 (2008) và vẫn là cách chính để nhận kết quả từ Activity trong hơn 12 năm. Tuy nhiên, phương thức này có những thiếu sót cơ bản mà Google đã khắc phục trong Activity Result API. Hãy xem xét các vấn đề chính và cách API mới giải quyết chúng.
Phương thức startActivityForResult bị ràng buộc với Activity và Fragment thông qua requestCode — một số nguyên tùy ý được truyền vào onActivityResult. Nhà phát triển phải tự đối sánh mã với hoạt động đã khởi chạy, dẫn đến lỗi khi tái sử dụng mã và kế thừa. ActivityResultLauncher loại bỏ hoàn toàn requestCode: callback được liên kết với một launcher cụ thể tại thời điểm đăng ký và chỉ được gọi cho nó.
Trong quá trình thay đổi cấu hình (xoay màn hình, thay đổi ngôn ngữ), Activity bị tạo lại và onActivityResult có thể không kích hoạt — callback bị mất. Activity Result API tự động lưu và khôi phục trạng thái launcher thông qua SavedStateRegistry, đảm bảo nhận được kết quả ngay cả sau khi Activity được tạo lại.
API cũ truyền kết quả qua Intent với Bundle, nơi các khóa và kiểu dữ liệu không được trình biên dịch kiểm tra. Activity Result API sử dụng Contract — một giao diện chung xác định kiểu dữ liệu đầu vào (I) và kiểu kết quả (O). Các lỗi không khớp kiểu sẽ được phát hiện tại thời điểm biên dịch, không phải lúc chạy.
| Đặc điểm | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | Cần quản lý thủ công | Tự động, không cần |
| An toàn kiểu | Không | Contract chung |
| Lưu khi xoay | Bị mất | SavedStateRegistry |
| API tối thiểu | API Level 1 | Activity 1.2.0 |
| Sử dụng trong Compose | Không hỗ trợ | rememberLauncherForActivityResult |
Contract là giao diện ActivityResultContract<I, O>, xác định cách khởi chạy Activity và cách diễn giải kết quả. Google cung cấp một tập hợp các contract tích hợp sẵn cho các kịch bản điển hình đáp ứng hầu hết nhu cầu của nhà phát triển.
StartIntentSenderForResult — một contract cơ bản để khởi chạy IntentSender. Được sử dụng trong các kịch bản hệ thống, ví dụ khi ủy quyền qua Google Sign-In hoặc thanh toán qua Google Pay. Tham số đầu vào là PendingIntent, đầu ra là ActivityResult với mã và Intent.
RequestMultiplePermissions — contract yêu cầu nhiều quyền cùng lúc trên Android 6.0+. Tham số đầu vào là mảng String chứa tên quyền, đầu ra là Map<String, Boolean> với kết quả của mỗi yêu cầu. Trước đây, việc này đòi hỏi phải phân tích thủ công trong onRequestPermissionsResult với việc đối sánh mã yêu cầu.
TakePicture — contract chụp ảnh qua camera hệ thống. Đầu vào là Uri để lưu ảnh, đầu ra là Boolean (thành công). TakeVideo hoạt động tương tự với video. Các contract này thay thế MediaStore.ACTION_IMAGE_CAPTURE đã lỗi thời với hành vi không ổn định trên các thiết bị khác nhau.
GetContent — contract chọn nội dung qua trình chọn hệ thống. Đầu vào là kiểu MIME (ví dụ image/*), đầu ra là Uri của tệp đã chọn. OpenDocument khác ở chỗ hỗ trợ chọn nhiều mục và lọc theo loại tài liệu. Cả hai contract đều hoạt động qua SAF (Storage Access Framework).
CreateDocument — contract tạo tài liệu mới qua hộp thoại hệ thống. Người dùng chọn tên và thư mục, hệ thống trả về Uri để ghi. OpenDocumentTree cung cấp quyền truy cập vào toàn bộ thư mục — người dùng chọn một thư mục và ứng dụng nhận được tree-uri để đọc và ghi tất cả các tệp bên trong.
Mô hình cơ bản để sử dụng ActivityResultLauncher trong Android cổ điển bao gồm hai bước: đăng ký qua registerForActivityResult khi khởi tạo và gọi launch để phản hồi hành động của người dùng. Hãy xem một ví dụ điển hình về chọn ảnh từ thư viện.
Đăng ký launcher trong onCreate của Activity — điều này đảm bảo callback sẵn sàng trước bất kỳ lời gọi nào. Không bao giờ đăng ký launcher ngay trước khi khởi chạy — điều này vi phạm contract API và có thể dẫn đến mất kết quả khi Activity được tạo lại.
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/*")
}
}
Trong Fragment, việc đăng ký được thực hiện trong onCreate, onAttach hoặc khởi tạo trong onCreateView. FragmentActivity truyền launcher qua Activity cha, vì vậy kết quả được xử lý bên trong Fragment, không phải trong Activity. Điều này cải thiện tính đóng gói so với onActivityResult, nơi tất cả kết quả từ tất cả Fragment được tập hợp trong một phương thức duy nhất của Activity.
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose cung cấp một hàm composable đặc biệt cho Activity Result API — rememberLauncherForActivityResult. Không giống cách tiếp cận cổ điển, trong Compose, launcher được tạo như một đối tượng gắn với vòng đời của composable thông qua remember. Điều này cho phép sử dụng Activity Result API hoàn toàn theo phong cách khai báo mà không cần truy cập trực tiếp vào Activity hoặc Fragment.
rememberLauncherForActivityResult nhận Contract và callback, trả về một ActivityResultLauncher. Launcher được giữ lại trong quá trình tái hợp thành và tự động dọn dẹp khi rời khỏi thành phần. Lời gọi launch xảy ra để phản hồi một sự kiện — ví dụ, nhấp nút hoặc thay đổi trạng thái.
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("Chọn ảnh")
}
}
Yêu cầu quyền trong Compose cũng được thực hiện qua rememberLauncherForActivityResult với contract RequestPermission hoặc RequestMultiplePermissions. Google khuyến nghị sử dụng accompanist-permissions, nhưng bên trong nó cũng sử dụng Activity Result API. Để theo dõi trạng thái quyền, bạn có thể lưu trữ trạng thái trong remember hoặc ViewModel.
Activity Result API đã loại bỏ nhiều vấn đề của cách tiếp cận cũ, nhưng sử dụng không đúng cách có thể dẫn đến các loại lỗi mới. Hãy xem các vấn đề phổ biến nhất và cách tránh chúng.
Việc đăng ký launcher phải được thực hiện trong quá trình khởi tạo thành phần — trong onCreate của Activity hoặc trình khởi tạo Fragment. Nếu bạn đăng ký launcher bên trong lambda, callback hoặc coroutine, khi Activity được tạo lại, việc đăng ký có thể được thực hiện lại và launcher cũ sẽ mất kết nối với kết quả.
Mỗi launcher nhận một khóa duy nhất để lưu trạng thái. Nếu bạn đăng ký hai launcher với cùng Contract trong một thành phần, SavedStateRegistry có thể ghi đè trạng thái của cái này lên cái kia. Android Studio cảnh báo về điều này thông qua quy tắc lint UnnecessaryRegisterForActivityResult, nhưng tốt hơn hết là kiểm soát tính duy nhất một cách thủ công.
Người dùng có thể hủy hành động — nhấn nút quay lại hệ thống, thu nhỏ ứng dụng hoặc chuyển sang ứng dụng khác. Trong trường hợp này, callback sẽ nhận được null hoặc ActivityResult với RESULT_CANCELED. Luôn kiểm tra kết quả có phải null không trước khi sử dụng để tránh NullPointerException.
Nếu ứng dụng của bạn thường xuyên khởi chạy các kịch bản tương tự — ví dụ, chọn một danh bạ và trả về tên và số điện thoại — hãy tạo một Contract tùy chỉnh. Điều này cải thiện khả năng đọc mã và cho phép thay đổi tập trung logic khởi chạy và xử lý kết quả.
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) }
}
Câu hỏi thường gặp
Không — ActivityResultLauncher yêu cầu ngữ cảnh Activity hoặc Fragment để đăng ký. Chỉ sử dụng ViewModel để lưu trạng thái và tạo launcher trong Activity hoặc Fragment rồi truyền kết quả vào ViewModel.
Activity Result API có sẵn từ thư viện activity-ktx 1.2.0. SDK tối thiểu là API Level 14 (Android 4.0), nhưng hầu hết các contract chỉ hoạt động trên API Level 19+.
Lời gọi lặp lại launch trước khi hoạt động đầu tiên hoàn thành sẽ bị bỏ qua. Activity Result API không hỗ trợ khởi chạy song song — hãy đợi callback từ hoạt động đầu tiên trước khi thực hiện lời gọi mới.
Việc di chuyển được thực hiện bằng cách thay thế lời gọi startActivityForResult bằng registerForActivityResult với Contract phù hợp. Xóa onActivityResult và xử lý kết quả trong callback của launcher. Google cung cấp hướng dẫn di chuyển trong tài liệu Android Developers.
Có, nhiều thư viện hỗ trợ tích hợp thông qua ActivityResultContracts. Ví dụ, ML Kit Barcode Scanner sử dụng StartIntentSenderForResult để khởi chạy máy quét. Hãy kiểm tra tài liệu của thư viện cụ thể.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm