ActivityResultLauncher is a component of the Android Activity Result API, introduced in version Activity 1.2.0 of the androidx.activity library. It replaces the deprecated startActivityForResult and onActivityResult methods, which have been part of the Android SDK since its inception. According to Android Developers (2024), the new API eliminates the problems of tight coupling with Activity and lack of type safety. ActivityResultLauncher is registered in advance and uses a Contract for strict typing of input and output data.
Key Takeaways
ActivityResultLauncher is a class from the androidx.activity.result package that provides a type-safe mechanism for launching an Activity and receiving a result. The launcher is created via the registerForActivityResult method, which takes two parameters: Contract (describes input and output types) and ActivityResultCallback (result handler). After registration, the launcher is ready to be called via the launch method.
The key difference from the old API is the separation of registration and launch. Registration is performed at the initialization stage (Activity.onCreate or Fragment.onCreate), the callback is bound to the launcher once and is guaranteed to fire when the result returns. This eliminates the problem where onActivityResult fired in an unexpected order or on a destroyed Activity.
ActivityResultLauncher supports all scenarios that were previously handled via onActivityResult: launching the camera, gallery, requesting contacts, permissions, and custom Activities. Furthermore, the API is extensible: developers can create custom Contracts for specific data exchange scenarios between Activities.
startActivityForResult has been part of the Android SDK since API Level 1 (2008) and remained the primary way to get a result from an Activity for over 12 years. However, this method had fundamental shortcomings that Google addressed in the Activity Result API. Let’s look at the main issues and how the new API solves them.
The startActivityForResult method is tied to Activity and Fragment via requestCode — an arbitrary integer passed to onActivityResult. The developer manually matched the code to the launched operation, leading to errors in code reuse and inheritance. ActivityResultLauncher completely eliminates requestCode: the callback is bound to a specific launcher at registration time and is only invoked for it.
During configuration changes (screen rotation, language change), the Activity was recreated and onActivityResult could fail to fire — the callback was lost. The Activity Result API automatically saves and restores the launcher state via SavedStateRegistry, ensuring the result is received even after Activity recreation.
The old API passed the result via Intent with a Bundle, where keys and data types were not checked by the compiler. The Activity Result API uses Contract — a generic interface that defines the input data type (I) and the result type (O). Type mismatch errors are caught at compile time, not at runtime.
| Feature | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | Manual management required | Automatic, not required |
| Type safety | No | Generic Contract |
| Saving on rotation | Lost | SavedStateRegistry |
| Minimum API | API Level 1 | Activity 1.2.0 |
| Usage in Compose | Not supported | rememberLauncherForActivityResult |
Contract is the ActivityResultContract<I, O> interface, which defines how to launch an Activity and how to interpret the result. Google provides a set of built-in contracts for typical scenarios covering most developer needs.
StartIntentSenderForResult — a basic contract for launching IntentSender. Used in system scenarios, for example when authorizing via Google Sign-In or making payments via Google Pay. The input parameter is PendingIntent, the output is ActivityResult with code and Intent.
RequestMultiplePermissions — a contract for requesting multiple permissions simultaneously on Android 6.0+. Input parameter is a String array with permission names, output is Map<String, Boolean> with the result of each request. Previously this required manual parsing in onRequestPermissionsResult with request code matching.
TakePicture — a contract for taking a photo via the system camera. Input is a Uri where to save the image, output is Boolean (success). TakeVideo works similarly with video. These contracts replace the deprecated MediaStore.ACTION_IMAGE_CAPTURE with unstable behavior on different devices.
GetContent — a contract for selecting content via the system picker. Input is a MIME type (e.g. image/*), output is a Uri of the selected file. OpenDocument differs by supporting multiple selection and filtering by document types. Both contracts work through SAF (Storage Access Framework).
CreateDocument — a contract for creating a new document via the system dialog. The user chooses a name and folder, the system returns a Uri for writing. OpenDocumentTree provides access to an entire directory — the user selects a folder, and the app receives a tree-uri for reading and writing all files inside.
The basic pattern for using ActivityResultLauncher in classic Android consists of two steps: registration via registerForActivityResult at initialization and calling launch in response to a user action. Let’s look at a typical example of selecting an image from the gallery.
Register the launcher in the Activity’s onCreate — this ensures the callback is ready before any possible call. Never register a launcher right before launching — this violates the API contract and can lead to result loss when the Activity is recreated.
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/*")
}
}
In a Fragment, registration is done in onCreate, onAttach, or initialization in onCreateView. FragmentActivity passes the launcher through the parent Activity, so the result is processed inside the Fragment, not in the Activity. This improves encapsulation compared to onActivityResult, where all results from all Fragments were gathered in one Activity method.
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose provides a special composable function for the Activity Result API — rememberLauncherForActivityResult. Unlike the classic approach, in Compose the launcher is created as an object bound to the composable lifecycle via remember. This allows using the Activity Result API entirely in a declarative style without direct access to Activity or Fragment.
rememberLauncherForActivityResult takes a Contract and a callback, returning an ActivityResultLauncher. The launcher is preserved during recomposition and automatically cleared when leaving composition. The launch call occurs in response to an event — for example, a button click or state change.
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("Choose Photo")
}
}
Permission requests in Compose are also done via rememberLauncherForActivityResult with the RequestPermission or RequestMultiplePermissions contract. Google recommends using accompanist-permissions, but under the hood it also uses the Activity Result API. To track permission state, it’s convenient to store the status in remember or ViewModel.
The Activity Result API eliminated many problems of the old approach, but improper use can lead to new kinds of errors. Let’s look at the most common issues and how to avoid them.
Registration of the launcher must be done during component initialization — in Activity onCreate or Fragment initializer. If you register the launcher inside a lambda, callback, or coroutine, upon Activity recreation the registration may be performed again and the old launcher will lose connection with the result.
Each launcher receives a unique key for state saving. If you register two launchers with the same Contract in one component, SavedStateRegistry may overwrite one state with another. Android Studio warns about this through the lint rule UnnecessaryRegisterForActivityResult, but it’s better to control uniqueness manually.
The user may cancel the action — press the system back button, minimize the app, or switch to another app. In this case, the callback will receive null or ActivityResult with RESULT_CANCELED. Always check the result for null before using it to avoid NullPointerException.
If your app frequently launches similar scenarios — for example, selecting a contact and returning a name and phone — create a custom Contract. This improves code readability and allows centralized changes to launch and result handling logic.
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) }
}
Frequently Asked Questions
No — ActivityResultLauncher requires an Activity or Fragment context for registration. Use ViewModel only for storing state, and create the launcher in Activity or Fragment and pass the result to ViewModel.
Activity Result API is available starting from library activity-ktx 1.2.0. The minimum SDK is API Level 14 (Android 4.0), but most contracts only work on API Level 19+.
A repeated call to launch before the first operation completes will be ignored. The Activity Result API does not support parallel launches — wait for the callback from the first operation before making a new call.
Migration is done by replacing the startActivityForResult call with registerForActivityResult using the appropriate Contract. Remove onActivityResult and handle the result in the launcher callback. Google provides a migration guide in the Android Developers documentation.
Yes, many libraries support integration through ActivityResultContracts. For example, ML Kit Barcode Scanner uses StartIntentSenderForResult to launch the scanner. Check the specific library’s documentation.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also