ActivityResultLauncher: What It Is and How to Use

Author: IT Sectr Published: 2026-06-10 Reading time: 9 min

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 — a new API for getting results from an Activity instead of the deprecated startActivityForResult
  • Contract — an object that defines the type of input and output data for a specific scenario
  • Registration is done through registerForActivityResult before calling launch
  • Callback is invoked after the target Activity finishes with a result
  • API available for Activity, Fragment and Compose starting from Activity 1.2.0

What is ActivityResultLauncher

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.

Why Activity Result API Replaced startActivityForResult

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.

Problem 1: Tight coupling with Activity

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.

Problem 2: Result loss on screen rotation

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.

Problem 3: Lack of type safety

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.

FeaturestartActivityForResultActivityResultLauncher
RequestCodeManual management requiredAutomatic, not required
Type safetyNoGeneric Contract
Saving on rotationLostSavedStateRegistry
Minimum APIAPI Level 1Activity 1.2.0
Usage in ComposeNot supportedrememberLauncherForActivityResult

Main Activity Result API Contracts

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

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

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 and TakeVideo

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 and OpenDocument

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 and OpenDocumentTree

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.

Usage in Activity and Fragment

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.

Registration and launch in Activity

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.

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

Usage in Fragment

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.

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

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

ActivityResultLauncher in Jetpack Compose

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

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.

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

    Button(onClick = { launcher.launch("image/*") }) {
        Text("Choose Photo")
    }
}

Permission handling in Compose

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.

Common Mistakes and Best Practices

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.

Mistake: registration inside a lambda or coroutine

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.

Mistake: registering multiple launchers with the same key

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.

Best Practice: always handle null result

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.

Best Practice: custom Contracts for reusable logic

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.

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

Frequently Asked Questions

Can ActivityResultLauncher be used in ViewModel?

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.

What minimum SDK is required for Activity Result API?

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

What happens if launch is called twice before receiving a result?

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.

How to replace onActivityResult in legacy code?

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.

Does ActivityResultLauncher work with libraries like ML Kit or Barcode Scanner?

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

  • ActivityResultLauncher — a modern type-safe API replacing the deprecated startActivityForResult
  • Contract defines the input and output data types, eliminating manual requestCode matching
  • Registration is performed at initialization, the result is guaranteed to be delivered via callback
  • Built-in contracts cover camera, gallery, permissions, documents, and contacts
  • Jetpack Compose uses rememberLauncherForActivityResult to work with the API
  • Custom Contracts allow reusing launch logic between components
  • API preserves state during configuration changes via SavedStateRegistry

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.

Discuss the project

Read also