ActivityResultLauncher este o componentă a Android Activity Result API, introdusă în versiunea Activity 1.2.0 a bibliotecii androidx.activity. Acesta înlocuiește metodele învechite startActivityForResult și onActivityResult, care făceau parte din Android SDK de la crearea sa. Potrivit Android Developers (2024), noul API elimină problemele de cuplare strânsă cu Activity și lipsa siguranței tipurilor. ActivityResultLauncher se înregistrează în avans și folosește Contract pentru tipizarea strictă a datelor de intrare și ieșire.
Principalele
ActivityResultLauncher este o clasă din pachetul androidx.activity.result, care oferă un mecanism sigur din punct de vedere al tipurilor pentru lansarea Activity și obținerea rezultatului. Launcher este creat prin metoda registerForActivityResult, care primește doi parametri: Contract (descrie tipurile de intrare și ieșire) și ActivityResultCallback (handlerul rezultatului). După înregistrare, launcher este gata pentru apel prin metoda launch.
Diferența cheie față de API-ul vechi — separarea înregistrării și lansării. Înregistrarea se face în etapa de inițializare (Activity.onCreate sau Fragment.onCreate), callback-ul este legat de launcher o singură dată și este garantat să se declanșeze la returnarea rezultatului. Aceasta elimină problema când onActivityResult se declanșa într-o ordine neașteptată sau pe un Activity distrus.
ActivityResultLauncher suportă toate scenariile care erau gestionate anterior prin onActivityResult: lansarea camerei, galeriei, solicitarea contactelor, permisiunilor și Activity-urilor personalizate. În plus, API-ul este extensibil: dezvoltatorul poate crea Contract-uri personalizate pentru scenarii specifice de schimb de date între Activity-uri.
startActivityForResult a fost parte din Android SDK de la versiunea API Level 1 (2008) și a rămas principala modalitate de a obține rezultatul de la Activity timp de peste 12 ani. Cu toate acestea, această metodă avea defecte fundamentale pe care Google le-a eliminat în Activity Result API. Să analizăm problemele principale și cum le rezolvă noul API.
Metoda startActivityForResult este legată de Activity și Fragment prin requestCode — un număr întreg arbitrar care este transmis în onActivityResult. Dezvoltatorul potrivea manual codul cu operația lansată, ceea ce ducea la erori la reutilizarea codului și moștenire. ActivityResultLauncher elimină complet requestCode: callback-ul este legat de un anumit launcher în etapa de înregistrare și este apelat doar pentru acesta.
La modificări de configurare (rotirea ecranului, schimbarea limbii) Activity era recreat, iar onActivityResult putea să nu se declanșeze — callback-ul se pierdea. Activity Result API salvează și restaurează automat starea launcher-ului prin SavedStateRegistry, ceea ce garantează primirea rezultatului chiar și după recrearea Activity.
API-ul vechi transmitea rezultatul prin Intent cu Bundle, unde cheile și tipurile de date nu erau verificate de compilator. Activity Result API folosește Contract — o interfață generică care definește tipul datelor de intrare (I) și tipul rezultatului (O). Erorile de nepotrivire a tipurilor sunt detectate în faza de compilare, nu la runtime.
| Caracteristică | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | Necesită gestionare manuală | Automat, nu este necesar |
| Siguranța tipurilor | Nu | Generic Contract |
| Salvare la rotire | Se pierde | SavedStateRegistry |
| API minim | API Level 1 | Activity 1.2.0 |
| Utilizare în Compose | Nu este suportat | rememberLauncherForActivityResult |
Contract — este o interfață ActivityResultContract<I, O>, care definește cum să lanseze Activity și cum să interpreteze rezultatul. Google oferă un set de contracte încorporate pentru scenarii tipice, acoperind majoritatea nevoilor dezvoltatorului.
StartIntentSenderForResult — contractul de bază pentru lansarea IntentSender. Folosit în scenarii de sistem, de exemplu la autentificarea prin Google Sign-In sau la plata prin Google Pay. Parametrul de intrare — PendingIntent, ieșire — ActivityResult cu cod și Intent.
RequestMultiplePermissions — contractul pentru solicitarea mai multor permisiuni simultan în Android 6.0+. Parametrul de intrare — un array String cu numele permisiunilor, ieșire — Map<String, Boolean> cu rezultatul fiecărei solicitări. Anterior, acest lucru necesita parsare manuală în onRequestPermissionsResult cu potrivirea codurilor de solicitare.
TakePicture — contractul pentru fotografierea prin camera sistem. Intrare — Uri unde se salvează fotografia, ieșire — Boolean (succes). TakeVideo funcționează similar cu video. Aceste contracte înlocuiesc învechitul MediaStore.ACTION_IMAGE_CAPTURE cu comportament instabil pe diferite dispozitive.
GetContent — contractul pentru selectarea conținutului prin selectorul de sistem. Intrare — tip MIME (de exemplu image/*), ieșire — Uri al fișierului selectat. OpenDocument se diferențiază prin suport pentru selecție multiplă și filtrare după tipurile de documente. Ambele contracte funcționează prin SAF (Storage Access Framework).
CreateDocument — contractul pentru crearea unui document nou prin dialogul de sistem. Utilizatorul alege numele și folderul, sistemul returnează Uri pentru scriere. OpenDocumentTree oferă acces la întregul director — utilizatorul alege un folder, iar aplicația primește tree-uri pentru citirea și scrierea tuturor fișierelor din interior.
Modelul de bază de utilizare a ActivityResultLauncher în Android clasic constă din doi pași: înregistrarea prin registerForActivityResult în etapa de inițializare și apelul launch ca răspuns la acțiunea utilizatorului. Să analizăm un exemplu tipic de selectare a unei imagini din galerie.
Înregistrați launcher-ul în onCreate al Activity — aceasta garantează că callback-ul va fi gata înainte de orice apel posibil. Nu înregistrați niciodată launcher-ul imediat înainte de lansare — aceasta încalcă contractul API și poate duce la pierderea rezultatului la recrearea 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/*")
}
}
În Fragment înregistrarea se face în onCreate, onAttach sau inițializare în onCreateView. FragmentActivity transmite launcher-ul prin Activity-ul părinte, astfel încât rezultatul este gestionat în interiorul Fragment, nu în Activity. Aceasta îmbunătățește încapsularea comparativ cu onActivityResult, unde toate rezultatele din toate Fragment-urile erau colectate într-o singură metodă a Activity.
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose oferă o funcționalitate composable specială pentru Activity Result API — rememberLauncherForActivityResult. Spre deosebire de abordarea clasică, în Compose launcher-ul este creat ca un obiect legat de ciclul de viață al composable-ului prin remember. Acest lucru permite utilizarea Activity Result API în stil complet declarativ fără acces direct la Activity sau Fragment.
rememberLauncherForActivityResult primește Contract și callback, returnând ActivityResultLauncher. Launcher-ul este păstrat la recompoziție și este curățat automat la ieșirea din compoziție. Apelul launch are loc ca răspuns la un eveniment — de exemplu, apăsarea unui buton sau schimbarea stării.
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("Alege fotografie")
}
}
Solicitarea permisiunilor în Compose se face, de asemenea, prin rememberLauncherForActivityResult cu contractul RequestPermission sau RequestMultiplePermissions. Google recomandă utilizarea accompanist-permissions, dar în spate folosește tot Activity Result API. Pentru controlul stării permisiunilor, este convenabil să stocați starea în remember sau ViewModel.
Activity Result API a eliminat multe probleme ale abordării vechi, dar utilizarea sa incorectă poate duce la noi tipuri de erori. Să analizăm cele mai frecvente probleme și modalitățile de a le evita.
Înregistrarea launcher-ului trebuie să aibă loc la inițializarea componentului — în onCreate al Activity sau inițializatorul Fragment. Dacă înregistrați launcher-ul în interiorul unei lambda, callback sau corutine, la recrearea Activity înregistrarea poate fi efectuată din nou, iar launcher-ul vechi va pierde legătura cu rezultatul.
Fiecare launcher primește o cheie unică pentru salvarea stării. Dacă înregistrați două launcher-uri cu același Contract într-un singur component, SavedStateRegistry poate suprascrie starea unuia peste celălalt. Android Studio avertizează despre acest lucru prin regula lint UnnecessaryRegisterForActivityResult, dar este mai bine să controlați unicitatea manual.
Utilizatorul poate anula acțiunea — apăsa butonul de înapoi al sistemului, minimiza aplicația sau comuta la o altă aplicație. În acest caz, callback-ul va primi null sau ActivityResult cu RESULT_CANCELED. Verificați întotdeauna rezultatul pentru null înainte de utilizare pentru a evita NullPointerException.
Dacă aplicația dvs. lansează frecvent scenarii similare — de exemplu, selectarea unui contact cu returnarea numelui și telefonului — creați propriul Contract. Aceasta îmbunătățește lizibilitatea codului și permite modificarea centralizată a logicii de lansare și gestionare a rezultatului.
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) }
}
Întrebări frecvente
Nu — ActivityResultLauncher necesită context de Activity sau Fragment pentru înregistrare. Folosiți ViewModel doar pentru stocarea stării, iar launcher-ul creați-l în Activity sau Fragment și transmiteți rezultatul în ViewModel.
Activity Result API este disponibil începând cu biblioteca activity-ktx 1.2.0. SDK minim — API Level 14 (Android 4.0), dar majoritatea contractelor funcționează doar pe API Level 19+.
Apelul repetat al launch înainte de finalizarea primei operații va fi ignorat. Activity Result API nu suportă lansări paralele — așteptați callback-ul de la prima operație înainte de un nou apel.
Migrarea se face prin înlocuirea apelului startActivityForResult cu registerForActivityResult cu Contract-ul corespunzător. Ștergeți onActivityResult și gestionați rezultatul în callback-ul launcher-ului. Google oferă un ghid de migrare în documentația Android Developers.
Da, multe biblioteci suportă integrarea prin ActivityResultContracts. De exemplu, ML Kit Barcode Scanner folosește StartIntentSenderForResult pentru lansarea scanerului. Verificați documentația bibliotecii specifice.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și