ActivityResultLauncher es un componente de la API Activity Result de Android, presentado en la versión Activity 1.2.0 de la biblioteca androidx.activity. Reemplaza los métodos obsoletos startActivityForResult y onActivityResult, que formaban parte del SDK de Android desde su creación. Según Android Developers (2024), la nueva API elimina los problemas de acoplamiento fuerte con Activity y la falta de seguridad de tipos. ActivityResultLauncher se registra de antemano y utiliza un Contract para la tipificación estricta de los datos de entrada y salida.
Puntos clave
ActivityResultLauncher es una clase del paquete androidx.activity.result que proporciona un mecanismo de seguridad de tipos para iniciar una Activity y recibir un resultado. El launcher se crea mediante el método registerForActivityResult, que acepta dos parámetros: Contract (describe los tipos de entrada y salida) y ActivityResultCallback (manejador del resultado). Después del registro, el launcher está listo para ser invocado mediante el método launch.
La diferencia clave con la API antigua es la separación del registro y el lanzamiento. El registro se realiza en la etapa de inicialización (Activity.onCreate o Fragment.onCreate), el callback se vincula al launcher una vez y se garantiza que se ejecute cuando regrese el resultado. Esto elimina el problema de que onActivityResult se ejecutara en un orden inesperado o en una Activity destruida.
ActivityResultLauncher admite todos los escenarios que antes se manejaban mediante onActivityResult: iniciar la cámara, la galería, solicitar contactos, permisos y Activities personalizadas. Además, la API es extensible: los desarrolladores pueden crear Contracts personalizados para escenarios específicos de intercambio de datos entre Activities.
startActivityForResult ha sido parte del SDK de Android desde API Level 1 (2008) y siguió siendo la forma principal de obtener un resultado de una Activity durante más de 12 años. Sin embargo, este método tenía deficiencias fundamentales que Google solucionó en Activity Result API. Veamos los principales problemas y cómo los resuelve la nueva API.
El método startActivityForResult está vinculado a Activity y Fragment mediante requestCode — un número entero arbitrario pasado a onActivityResult. El desarrollador emparejaba manualmente el código con la operación iniciada, lo que provocaba errores en la reutilización de código y la herencia. ActivityResultLauncher elimina por completo requestCode: el callback se vincula a un launcher específico en el momento del registro y solo se invoca para él.
Durante los cambios de configuración (rotación de pantalla, cambio de idioma), la Activity se recreaba y onActivityResult podía no ejecutarse — el callback se perdía. Activity Result API guarda y restaura automáticamente el estado del launcher mediante SavedStateRegistry, lo que garantiza que se reciba el resultado incluso después de recrear la Activity.
La API antigua pasaba el resultado mediante Intent con un Bundle, donde las claves y los tipos de datos no eran verificados por el compilador. Activity Result API utiliza Contract — una interfaz genérica que define el tipo de datos de entrada (I) y el tipo de resultado (O). Los errores de discrepancia de tipos se detectan en tiempo de compilación, no en tiempo de ejecución.
| Característica | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | Gestión manual requerida | Automático, no requerido |
| Seguridad de tipos | No | Contract genérico |
| Guardado al rotar | Se pierde | SavedStateRegistry |
| API mínima | API Level 1 | Activity 1.2.0 |
| Uso en Compose | No compatible | rememberLauncherForActivityResult |
Contract es la interfaz ActivityResultContract<I, O>, que define cómo iniciar una Activity y cómo interpretar el resultado. Google proporciona un conjunto de contratos integrados para escenarios típicos que cubren la mayoría de las necesidades del desarrollador.
StartIntentSenderForResult — un contrato básico para iniciar IntentSender. Se utiliza en escenarios del sistema, por ejemplo al autorizar mediante Google Sign-In o realizar pagos con Google Pay. El parámetro de entrada es PendingIntent, la salida es ActivityResult con código e Intent.
RequestMultiplePermissions — un contrato para solicitar múltiples permisos simultáneamente en Android 6.0+. El parámetro de entrada es un array de String con nombres de permisos, la salida es Map<String, Boolean> con el resultado de cada solicitud. Anteriormente esto requería análisis manual en onRequestPermissionsResult con coincidencia de códigos de solicitud.
TakePicture — un contrato para tomar una foto mediante la cámara del sistema. La entrada es un Uri donde guardar la imagen, la salida es Boolean (éxito). TakeVideo funciona de manera similar con video. Estos contratos reemplazan el obsoleto MediaStore.ACTION_IMAGE_CAPTURE con comportamiento inestable en diferentes dispositivos.
GetContent — un contrato para seleccionar contenido mediante el selector del sistema. La entrada es un tipo MIME (por ejemplo, image/*), la salida es un Uri del archivo seleccionado. OpenDocument se diferencia por admitir selección múltiple y filtrado por tipos de documento. Ambos contratos funcionan a través de SAF (Storage Access Framework).
CreateDocument — un contrato para crear un nuevo documento mediante el diálogo del sistema. El usuario elige un nombre y una carpeta, el sistema devuelve un Uri para escribir. OpenDocumentTree proporciona acceso a un directorio completo — el usuario selecciona una carpeta y la aplicación recibe un tree-uri para leer y escribir todos los archivos dentro.
El patrón básico para usar ActivityResultLauncher en Android clásico consta de dos pasos: registro mediante registerForActivityResult en la inicialización y llamada a launch en respuesta a una acción del usuario. Veamos un ejemplo típico de selección de una imagen de la galería.
Registra el launcher en onCreate de la Activity — esto garantiza que el callback esté listo antes de cualquier posible llamada. Nunca registres un launcher justo antes de lanzarlo — esto viola el contrato de la API y puede provocar la pérdida del resultado al recrear la Activity.
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/*")
}
}
En un Fragment, el registro se realiza en onCreate, onAttach o en la inicialización en onCreateView. FragmentActivity pasa el launcher a través de la Activity padre, por lo que el resultado se procesa dentro del Fragment, no en la Activity. Esto mejora la encapsulación en comparación con onActivityResult, donde todos los resultados de todos los Fragments se reunían en un solo método de Activity.
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose proporciona una función composable especial para Activity Result API — rememberLauncherForActivityResult. A diferencia del enfoque clásico, en Compose el launcher se crea como un objeto vinculado al ciclo de vida del composable mediante remember. Esto permite usar Activity Result API completamente en un estilo declarativo sin acceso directo a Activity o Fragment.
rememberLauncherForActivityResult acepta un Contract y un callback, devolviendo un ActivityResultLauncher. El launcher se conserva durante la recomposición y se limpia automáticamente al salir de la composición. La llamada a launch ocurre en respuesta a un evento — por ejemplo, un clic en un botón o un cambio de estado.
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("Elegir foto")
}
}
Las solicitudes de permisos en Compose también se realizan mediante rememberLauncherForActivityResult con el contrato RequestPermission o RequestMultiplePermissions. Google recomienda usar accompanist-permissions, pero internamente también utiliza Activity Result API. Para controlar el estado de los permisos, es conveniente almacenar el estado en remember o ViewModel.
Activity Result API eliminó muchos problemas del enfoque anterior, pero un uso incorrecto puede provocar nuevos tipos de errores. Veamos los problemas más comunes y cómo evitarlos.
El registro del launcher debe realizarse durante la inicialización del componente — en onCreate de Activity o en el inicializador de Fragment. Si registras el launcher dentro de una lambda, callback o corrutina, al recrear la Activity el registro puede realizarse de nuevo y el launcher anterior perderá la conexión con el resultado.
Cada launcher recibe una clave única para guardar el estado. Si registras dos launchers con el mismo Contract en un componente, SavedStateRegistry puede sobrescribir el estado de uno con el del otro. Android Studio advierte sobre esto mediante la regla lint UnnecessaryRegisterForActivityResult, pero es mejor controlar la unicidad manualmente.
El usuario puede cancelar la acción — presionar el botón de retroceso del sistema, minimizar la aplicación o cambiar a otra aplicación. En este caso, el callback recibirá null o ActivityResult con RESULT_CANCELED. Siempre verifica si el resultado es null antes de usarlo para evitar NullPointerException.
Si tu aplicación inicia con frecuencia escenarios similares — por ejemplo, seleccionar un contacto y devolver un nombre y teléfono — crea un Contract personalizado. Esto mejora la legibilidad del código y permite cambios centralizados en la lógica de inicio y manejo de resultados.
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) }
}
Preguntas frecuentes
No — ActivityResultLauncher requiere un contexto de Activity o Fragment para registrarse. Usa ViewModel solo para almacenar el estado, y crea el launcher en Activity o Fragment y pasa el resultado a ViewModel.
Activity Result API está disponible desde la biblioteca activity-ktx 1.2.0. El SDK mínimo es API Level 14 (Android 4.0), pero la mayoría de los contratos solo funcionan en API Level 19+.
Una llamada repetida a launch antes de que finalice la primera operación será ignorada. Activity Result API no admite lanzamientos paralelos — espera el callback de la primera operación antes de realizar una nueva llamada.
La migración se realiza reemplazando la llamada a startActivityForResult por registerForActivityResult con el Contract correspondiente. Elimina onActivityResult y maneja el resultado en el callback del launcher. Google proporciona una guía de migración en la documentación de Android Developers.
Sí, muchas bibliotecas admiten la integración mediante ActivityResultContracts. Por ejemplo, ML Kit Barcode Scanner utiliza StartIntentSenderForResult para iniciar el escáner. Consulta la documentación de la biblioteca específica.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también