ActivityResultLauncher: nó là gì và cách sử dụng

Tác giả: IT Sectr Đã đăng: 2026-06-10 Thời gian đọc: 9 phút

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 — API mới để nhận kết quả từ Activity thay vì startActivityForResult đã lỗi thời
  • Contract — một đối tượng xác định kiểu dữ liệu đầu vào và đầu ra cho một kịch bản cụ thể
  • Đăng ký được thực hiện qua registerForActivityResult trước khi gọi launch
  • Callback được gọi sau khi Activity mục tiêu kết thúc với một kết quả
  • API khả dụng cho Activity, Fragment và Compose bắt đầu từ Activity 1.2.0

ActivityResultLauncher là gì

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.

Tại sao Activity Result API thay thế startActivityForResult

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.

Vấn đề 1: Gắn kết chặt với Activity

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ó.

Vấn đề 2: Mất kết quả khi xoay màn hình

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.

Vấn đề 3: Thiếu an toàn kiểu dữ liệu

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ểmstartActivityForResultActivityResultLauncher
RequestCodeCần quản lý thủ côngTự động, không cần
An toàn kiểuKhôngContract chung
Lưu khi xoayBị mấtSavedStateRegistry
API tối thiểuAPI Level 1Activity 1.2.0
Sử dụng trong ComposeKhông hỗ trợrememberLauncherForActivityResult

Các contract chính của Activity Result API

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

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

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 và TakeVideo

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 và OpenDocument

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 và OpenDocumentTree

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.

Sử dụng trong Activity và Fragment

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ý và khởi chạy trong Activity

Đă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.

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

Sử dụng trong Fragment

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.

kotlin
class ProfileFragment : Fragment() {
    private val cameraLauncher =
        registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
            if (success) { updateProfilePhoto() }
        }

    fun takePhoto(photoUri: Uri) {
        cameraLauncher.launch(photoUri)
    }
}

ActivityResultLauncher trong Jetpack Compose

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

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.

kotlin
@Composable
fun PhotoPicker() {
    val context = LocalContext.current
    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri -> handleImage(uri) }

    Button(onClick = { launcher.launch("image/*") }) {
        Text("Chọn ảnh")
    }
}

Xử lý quyền trong Compose

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.

Lỗi thường gặp và các thực hành tốt nhất

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.

Lỗi: đăng ký bên trong lambda hoặc coroutine

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ả.

Lỗi: đăng ký nhiều launcher với cùng một khóa

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.

Thực hành tốt: luôn xử lý kết quả null

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.

Thực hành tốt: Contract tùy chỉnh cho logic có thể tái sử dụng

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ả.

kotlin
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

Có thể sử dụng ActivityResultLauncher trong ViewModel không?

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.

Yêu cầu SDK tối thiểu nào cho Activity Result API?

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+.

Điều gì xảy ra nếu launch được gọi hai lần trước khi nhận được kết quả?

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.

Làm thế nào để thay thế onActivityResult trong mã cũ?

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.

ActivityResultLauncher có hoạt động với các thư viện như ML Kit hoặc Barcode Scanner không?

, 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

  • ActivityResultLauncher — API hiện đại, an toàn về kiểu thay thế startActivityForResult đã lỗi thời
  • Contract xác định kiểu dữ liệu đầu vào và đầu ra, loại bỏ việc đối sánh requestCode thủ công
  • Đăng ký được thực hiện khi khởi tạo, kết quả được đảm bảo gửi qua callback
  • Contract tích hợp sẵn bao gồm camera, thư viện ảnh, quyền, tài liệu và danh bạ
  • Jetpack Compose sử dụng rememberLauncherForActivityResult để làm việc với API
  • Contract tùy chỉnh cho phép tái sử dụng logic khởi chạy giữa các thành phần
  • API lưu trạng thái khi thay đổi cấu hình thông qua SavedStateRegistry

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.

Thảo luận dự án

Đọc thêm