ActivityResultLauncher to komponent Android Activity Result API, wprowadzony w wersji Activity 1.2.0 biblioteki androidx.activity. Zastępuje przestarzałe metody startActivityForResult i onActivityResult, które były częścią Android SDK od momentu jego powstania. Według Android Developers (2024), nowy API eliminuje problemy ścisłego powiązania z Activity i braku bezpieczeństwa typów. ActivityResultLauncher jest rejestrowany z wyprzedzeniem i używa Contract dla ścisłej typizacji danych wejściowych i wyjściowych.
Najważniejsze
ActivityResultLauncher to klasa z pakietu androidx.activity.result, która zapewnia mechanizm bezpieczny typowo do uruchamiania Activity i otrzymywania wyniku. Launcher jest tworzony przez metodę registerForActivityResult, przyjmującą dwa parametry: Contract (opisuje typy wejściowe i wyjściowe) oraz ActivityResultCallback (handler wyniku). Po rejestracji launcher jest gotowy do wywołania przez metodę launch.
Kluczowa różnica w stosunku do starego API to rozdzielenie rejestracji i uruchamiania. Rejestracja odbywa się na etapie inicjalizacji (Activity.onCreate lub Fragment.onCreate), callback jest powiązany z launcher raz i gwarantowanie wywołuje się przy zwrocie wyniku. Eliminuje to problem, gdy onActivityResult wywoływał się w nieoczekiwanej kolejności lub na zniszczonym Activity.
ActivityResultLauncher obsługuje wszystkie scenariusze, które wcześniej były obsługiwane przez onActivityResult: uruchamianie aparatu, galerii, zapytanie o kontakty, uprawnienia i niestandardowe Activity. Ponadto API jest rozszerzalny: programista może tworzyć własne Contract dla specyficznych scenariuszy wymiany danych między Activity.
startActivityForResult był częścią Android SDK od wersji API Level 1 (2008) i pozostawał głównym sposobem uzyskiwania wyniku z Activity przez ponad 12 lat. Jednak ta metoda miała fundamentalne wady, które Google wyeliminowało w Activity Result API. Przyjrzyjmy się głównym problemom i jak nowy API je rozwiązuje.
Metoda startActivityForResult jest powiązana z Activity i Fragment przez requestCode — dowolną liczbę całkowitą, która jest przekazywana do onActivityResult. Programista ręcznie dopasowywał kod do uruchomionej operacji, co prowadziło do błędów przy ponownym użyciu kodu i dziedziczeniu. ActivityResultLauncher całkowicie eliminuje requestCode: callback jest powiązany z konkretnym launcher na etapie rejestracji i wywoływany tylko dla niego.
Przy zmianach konfiguracji (obrót ekranu, zmiana języka) Activity było odtwarzane, a onActivityResult mógł nie zadziałać — callback ginął. Activity Result API automatycznie zapisuje i przywraca stan launcher przez SavedStateRegistry, co gwarantuje otrzymanie wyniku nawet po odtworzeniu Activity.
Stary API przekazywał wynik przez Intent z Bundle, gdzie klucze i typy danych nie były sprawdzane przez kompilator. Activity Result API używa Contract — interfejsu generycznego, który określa typ danych wejściowych (I) i typ wyniku (O). Błędy niezgodności typów są wykrywane na etapie kompilacji, a nie w runtime.
| Cecha | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | Wymaga ręcznego zarządzania | Automatyczny, niewymagany |
| Bezpieczeństwo typów | Nie | Generic Contract |
| Zachowanie przy obrocie | Tracone | SavedStateRegistry |
| Minimalny API | API Level 1 | Activity 1.2.0 |
| Użycie w Compose | Nieobsługiwane | rememberLauncherForActivityResult |
Contract to interfejs ActivityResultContract<I, O>, który określa, jak uruchomić Activity i jak interpretować wynik. Google dostarcza zestaw wbudowanych kontraktów dla typowych scenariuszy, pokrywających większość potrzeb programisty.
StartIntentSenderForResult to podstawowy kontrakt do uruchamiania IntentSender. Używany w scenariuszach systemowych, na przykład przy autoryzacji przez Google Sign-In lub przy płatności przez Google Pay. Parametr wejściowy — PendingIntent, wyjściowy — ActivityResult z kodem i Intent.
RequestMultiplePermissions to kontrakt do żądania wielu uprawnień jednocześnie w Android 6.0+. Parametr wejściowy — tablica String z nazwami uprawnień, wyjściowy — Map<String, Boolean> z wynikiem każdego żądania. Wcześniej wymagało to ręcznego parsowania w onRequestPermissionsResult z dopasowaniem kodów żądań.
TakePicture to kontrakt do robienia zdjęć przez systemowy aparat. Wejście — Uri, gdzie zapisać zdjęcie, wyjście — Boolean (sukces). TakeVideo analogicznie działa z wideo. Te kontrakty zastępują przestarzały MediaStore.ACTION_IMAGE_CAPTURE z niestabilnym zachowaniem na różnych urządzeniach.
GetContent to kontrakt do wyboru treści przez systemowy picker. Wejście — typ MIME (na przykład image/*), wyjście — Uri wybranego pliku. OpenDocument różni się obsługą wielokrotnego wyboru i filtrowaniem po typach dokumentów. Oba kontrakty działają przez SAF (Storage Access Framework).
CreateDocument to kontrakt do tworzenia nowego dokumentu przez systemowe okno dialogowe. Użytkownik wybiera nazwę i folder, system zwraca Uri do zapisu. OpenDocumentTree daje dostęp do całego katalogu — użytkownik wybiera folder, a aplikacja otrzymuje tree-uri do odczytu i zapisu wszystkich plików wewnątrz.
Podstawowy wzorzec użycia ActivityResultLauncher w klasycznym Android składa się z dwóch kroków: rejestracja przez registerForActivityResult na etapie inicjalizacji i wywołanie launch w odpowiedzi na działanie użytkownika. Rozważmy typowy przykład wyboru obrazu z galerii.
Rejestruj launcher w onCreate Activity — to gwarantuje, że callback będzie gotowy przed każdym możliwym wywołaniem. Nigdy nie rejestruj launcher bezpośrednio przed uruchomieniem — to narusza kontrakt API i może prowadzić do utraty wyniku przy odtworzeniu 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/*")
}
}
W Fragment rejestracja odbywa się w onCreate, onAttach lub inicjalizacji w onCreateView. FragmentActivity przekazuje launcher przez rodzicielskie Activity, więc wynik jest obsługiwany wewnątrz Fragment, a nie w Activity. Poprawia to enkapsulację w porównaniu z onActivityResult, gdzie wszystkie wyniki ze wszystkich Fragment były zbierane w jednej metodzie Activity.
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose zapewnia specjalną funkcjonalność composable dla Activity Result API — rememberLauncherForActivityResult. W przeciwieństwie do klasycznego podejścia, w Compose launcher jest tworzony jako obiekt powiązany z cyklem życia composable przez remember. Pozwala to używać Activity Result API w pełni deklaratywnie bez bezpośredniego dostępu do Activity lub Fragment.
rememberLauncherForActivityResult przyjmuje Contract i callback, zwracając ActivityResultLauncher. Launcher jest zachowany przy rekompozycji i automatycznie czyszczony przy opuszczeniu kompozycji. Wywołanie launch następuje w odpowiedzi na zdarzenie — na przykład kliknięcie przycisku lub zmianę stanu.
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("Wybierz zdjęcie")
}
}
Żądanie uprawnień w Compose również odbywa się przez rememberLauncherForActivityResult z kontraktem RequestPermission lub RequestMultiplePermissions. Google zaleca używanie accompanist-permissions, ale pod maską również używa Activity Result API. Do kontroli stanu uprawnień wygodnie przechowywać status w remember lub ViewModel.
Activity Result API wyeliminował wiele problemów starego podejścia, ale jego nieprawidłowe użycie może prowadzić do nowych rodzajów błędów. Rozważmy najczęstsze problemy i sposoby ich unikania.
Rejestracja launcher musi odbywać się przy inicjalizacji komponentu — w onCreate Activity lub inicjalizatorze Fragment. Jeśli rejestrować launcher wewnątrz lambdy, callbacka lub korutyny, przy odtworzeniu Activity rejestracja może być wykonana ponownie, a stary launcher straci połączenie z wynikiem.
Każdy launcher otrzymuje unikalny klucz do zapisu stanu. Jeśli zarejestrować dwa launcher z tym samym Contract w jednym komponencie, SavedStateRegistry może nadpisać stan jednego drugim. Android Studio ostrzega o tym przez regułę lint UnnecessaryRegisterForActivityResult, ale lepiej kontrolować unikalność ręcznie.
Użytkownik może anulować działanie — nacisnąć systemowy przycisk wstecz, zminimalizować aplikację lub przełączyć się na inną aplikację. W tym przypadku callback otrzyma null lub ActivityResult z RESULT_CANCELED. Zawsze sprawdzaj wynik na null przed użyciem, aby uniknąć NullPointerException.
Jeśli twoja aplikacja często uruchamia podobne scenariusze — na przykład wybór kontaktu z zwrotem imienia i telefonu — utwórz własny Contract. Poprawia to czytelność kodu i pozwala centralnie zmieniać logikę uruchamiania i obsługi wyniku.
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) }
}
Często zadawane pytania
Nie — ActivityResultLauncher wymaga kontekstu Activity lub Fragment do rejestracji. Używaj ViewModel tylko do przechowywania stanu, a launcher twórz w Activity lub Fragment i przekazuj wynik do ViewModel.
Activity Result API jest dostępny począwszy od biblioteki activity-ktx 1.2.0. Minimalny SDK — API Level 14 (Android 4.0), ale większość kontraktów działa tylko na API Level 19+.
Ponowne wywołanie launch przed zakończeniem pierwszej operacji zostanie zignorowane. Activity Result API nie obsługuje równoległych uruchomień — poczekaj na callback od pierwszej operacji przed nowym wywołaniem.
Migracja odbywa się przez zastąpienie wywołania startActivityForResult na registerForActivityResult z odpowiednim Contract. Usuń onActivityResult i obsługuj wynik w callback launcher. Google udostępnia przewodnik migracji w dokumentacji Android Developers.
Tak, wiele bibliotek obsługuje integrację przez ActivityResultContracts. Na przykład ML Kit Barcode Scanner używa StartIntentSenderForResult do uruchomienia skanera. Sprawdź dokumentację konkretnej biblioteki.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również