ActivityResultLauncher è un componente dell’API Activity Result di Android, introdotto nella versione Activity 1.2.0 della libreria androidx.activity. Sostituisce i metodi deprecati startActivityForResult e onActivityResult, che facevano parte dell’SDK Android fin dalla sua creazione. Secondo Android Developers (2024), la nuova API elimina i problemi di forte accoppiamento con Activity e la mancanza di type safety. ActivityResultLauncher viene registrato in anticipo e utilizza un Contract per la tipizzazione rigorosa dei dati di input e output.
Punti chiave
ActivityResultLauncher è una classe del pacchetto androidx.activity.result che fornisce un meccanismo type-safe per avviare un’Activity e ricevere un risultato. Il launcher viene creato tramite il metodo registerForActivityResult, che accetta due parametri: Contract (descrive i tipi di input e output) e ActivityResultCallback (gestore del risultato). Dopo la registrazione, il launcher è pronto per essere chiamato tramite il metodo launch.
La differenza chiave rispetto alla vecchia API è la separazione tra registrazione e avvio. La registrazione viene eseguita in fase di inizializzazione (Activity.onCreate o Fragment.onCreate), il callback viene associato al launcher una volta e viene garantito che si attivi quando il risultato torna. Questo elimina il problema per cui onActivityResult si attivava in un ordine imprevisto o su un’Activity distrutta.
ActivityResultLauncher supporta tutti gli scenari che venivano precedentemente gestiti tramite onActivityResult: avviare la fotocamera, la galleria, richiedere contatti, permessi e Activity personalizzate. Inoltre, l’API è estensibile: gli sviluppatori possono creare Contract personalizzati per scenari specifici di scambio di dati tra Activity.
startActivityForResult ha fatto parte dell’SDK Android da API Level 1 (2008) ed è rimasto il metodo principale per ottenere un risultato da un’Activity per oltre 12 anni. Tuttavia, questo metodo aveva carenze fondamentali che Google ha risolto in Activity Result API. Esaminiamo i problemi principali e come la nuova API li risolve.
Il metodo startActivityForResult è legato ad Activity e Fragment tramite requestCode — un numero intero arbitrario passato a onActivityResult. Lo sviluppatore faceva corrispondere manualmente il codice all’operazione avviata, causando errori nel riutilizzo del codice e nell’ereditarietà. ActivityResultLauncher elimina completamente requestCode: il callback viene associato a un launcher specifico al momento della registrazione e viene invocato solo per quello.
Durante i cambiamenti di configurazione (rotazione dello schermo, cambio lingua), l’Activity veniva ricreata e onActivityResult poteva non attivarsi — il callback veniva perso. Activity Result API salva e ripristina automaticamente lo stato del launcher tramite SavedStateRegistry, garantendo la ricezione del risultato anche dopo la ricreazione dell’Activity.
La vecchia API passava il risultato tramite Intent con un Bundle, dove chiavi e tipi di dati non venivano controllati dal compilatore. Activity Result API utilizza Contract — un’interfaccia generica che definisce il tipo di dati di input (I) e il tipo di risultato (O). Gli errori di incompatibilità dei tipi vengono rilevati in fase di compilazione, non in fase di esecuzione.
| Caratteristica | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | Gestione manuale richiesta | Automatico, non richiesto |
| Type safety | No | Contract generico |
| Salvataggio in rotazione | Perso | SavedStateRegistry |
| API minima | API Level 1 | Activity 1.2.0 |
| Utilizzo in Compose | Non supportato | rememberLauncherForActivityResult |
Contract è l’interfaccia ActivityResultContract<I, O>, che definisce come avviare un’Activity e come interpretare il risultato. Google fornisce un set di contratti integrati per scenari tipici che coprono la maggior parte delle esigenze degli sviluppatori.
StartIntentSenderForResult — un contratto di base per avviare IntentSender. Utilizzato in scenari di sistema, ad esempio durante l’autorizzazione tramite Google Sign-In o i pagamenti tramite Google Pay. Il parametro di input è PendingIntent, l’output è ActivityResult con codice e Intent.
RequestMultiplePermissions — un contratto per richiedere più permessi contemporaneamente su Android 6.0+. Il parametro di input è un array di String con i nomi dei permessi, l’output è Map<String, Boolean> con il risultato di ogni richiesta. In precedenza ciò richiedeva l’analisi manuale in onRequestPermissionsResult con corrispondenza dei codici di richiesta.
TakePicture — un contratto per scattare una foto tramite la fotocamera di sistema. L’input è un Uri dove salvare l’immagine, l’output è Boolean (successo). TakeVideo funziona in modo simile con i video. Questi contratti sostituiscono il deprecato MediaStore.ACTION_IMAGE_CAPTURE dal comportamento instabile su diversi dispositivi.
GetContent — un contratto per selezionare contenuti tramite il selettore di sistema. L’input è un tipo MIME (ad esempio image/*), l’output è un Uri del file selezionato. OpenDocument si differenzia supportando la selezione multipla e il filtraggio per tipo di documento. Entrambi i contratti funzionano tramite SAF (Storage Access Framework).
CreateDocument — un contratto per creare un nuovo documento tramite il dialogo di sistema. L’utente sceglie un nome e una cartella, il sistema restituisce un Uri per la scrittura. OpenDocumentTree fornisce l’accesso a un’intera directory — l’utente seleziona una cartella e l’app riceve un tree-uri per leggere e scrivere tutti i file al suo interno.
Il modello di base per utilizzare ActivityResultLauncher in Android classico consiste in due passaggi: registrazione tramite registerForActivityResult in fase di inizializzazione e chiamata di launch in risposta a un’azione dell’utente. Esaminiamo un esempio tipico di selezione di un’immagine dalla galleria.
Registra il launcher nell’onCreate dell’Activity — questo garantisce che il callback sia pronto prima di qualsiasi possibile chiamata. Non registrare mai un launcher immediatamente prima di avviarlo — questo viola il contratto dell’API e può portare alla perdita del risultato durante la ricreazione dell’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/*")
}
}
In un Fragment, la registrazione viene effettuata in onCreate, onAttach o nell’inizializzazione in onCreateView. FragmentActivity passa il launcher tramite l’Activity genitore, quindi il risultato viene elaborato all’interno del Fragment, non nell’Activity. Questo migliora l’incapsulamento rispetto a onActivityResult, dove tutti i risultati di tutti i Fragment venivano raccolti in un unico metodo dell’Activity.
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose fornisce una funzione composable speciale per Activity Result API — rememberLauncherForActivityResult. A differenza dell’approccio classico, in Compose il launcher viene creato come un oggetto legato al ciclo di vita del composable tramite remember. Ciò consente di utilizzare Activity Result API completamente in stile dichiarativo senza accesso diretto ad Activity o Fragment.
rememberLauncherForActivityResult accetta un Contract e un callback, restituendo un ActivityResultLauncher. Il launcher viene preservato durante la ricomposizione e automaticamente cancellato quando si esce dalla composizione. La chiamata a launch avviene in risposta a un evento — ad esempio, un clic su un pulsante o un cambiamento di stato.
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("Scegli foto")
}
}
Le richieste di permessi in Compose vengono effettuate anch’esse tramite rememberLauncherForActivityResult con il contratto RequestPermission o RequestMultiplePermissions. Google raccomanda di utilizzare accompanist-permissions, ma internamente utilizza anch’esso Activity Result API. Per tenere traccia dello stato dei permessi, è conveniente memorizzare lo stato in remember o ViewModel.
Activity Result API ha eliminato molti problemi dell’approccio precedente, ma un utilizzo improprio può portare a nuovi tipi di errori. Esaminiamo i problemi più comuni e come evitarli.
La registrazione del launcher deve essere effettuata durante l’inizializzazione del componente — nell’onCreate dell’Activity o nell’inizializzatore del Fragment. Se registri il launcher all’interno di una lambda, un callback o una coroutine, durante la ricreazione dell’Activity la registrazione potrebbe essere eseguita nuovamente e il vecchio launcher perderà la connessione con il risultato.
Ogni launcher riceve una chiave univoca per il salvataggio dello stato. Se registri due launcher con lo stesso Contract in un componente, SavedStateRegistry potrebbe sovrascrivere lo stato dell’uno con quello dell’altro. Android Studio avvisa di ciò tramite la regola lint UnnecessaryRegisterForActivityResult, ma è meglio controllare l’unicità manualmente.
L’utente può annullare l’azione — premere il pulsante indietro di sistema, ridurre a icona l’app o passare a un’altra app. In questo caso, il callback riceverà null o ActivityResult con RESULT_CANCELED. Controlla sempre se il risultato è null prima di utilizzarlo per evitare NullPointerException.
Se la tua app avvia frequentemente scenari simili — ad esempio, selezionare un contatto e restituire nome e telefono — crea un Contract personalizzato. Ciò migliora la leggibilità del codice e consente modifiche centralizzate alla logica di avvio e gestione dei risultati.
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) }
}
Domande frequenti
No — ActivityResultLauncher richiede un contesto Activity o Fragment per la registrazione. Usa ViewModel solo per memorizzare lo stato e crea il launcher in Activity o Fragment e passa il risultato al ViewModel.
Activity Result API è disponibile a partire dalla libreria activity-ktx 1.2.0. L’SDK minimo è API Level 14 (Android 4.0), ma la maggior parte dei contratti funziona solo su API Level 19+.
Una chiamata ripetuta di launch prima del completamento della prima operazione verrà ignorata. Activity Result API non supporta avvii paralleli — attendi il callback della prima operazione prima di effettuare una nuova chiamata.
La migrazione viene effettuata sostituendo la chiamata startActivityForResult con registerForActivityResult utilizzando il Contract appropriato. Rimuovi onActivityResult e gestisci il risultato nel callback del launcher. Google fornisce una guida alla migrazione nella documentazione di Android Developers.
Sì, molte librerie supportano l’integrazione tramite ActivityResultContracts. Ad esempio, ML Kit Barcode Scanner utilizza StartIntentSenderForResult per avviare lo scanner. Controlla la documentazione della libreria specifica.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche