ActivityResultLauncher е компонент на Android Activity Result API, представен във версия Activity 1.2.0 на библиотеката androidx.activity. Той заменя остарелите методи startActivityForResult и onActivityResult, които бяха част от Android SDK от създаването му. Според Android Developers (2024), новият API премахва проблемите с тясната връзка с Activity и липсата на типова безопасност. ActivityResultLauncher се регистрира предварително и използва Contract за строго типизиране на входните и изходните данни.
Основни точки
ActivityResultLauncher е клас от пакета androidx.activity.result, който предоставя типобезопасен механизъм за стартиране на Activity и получаване на резултата. Launcher се създава чрез метода registerForActivityResult, който приема два параметъра: Contract (описва входните и изходните типове) и ActivityResultCallback (обработчик на резултата). След регистрация launcher е готов за извикване чрез метода launch.
Ключовата разлика от стария API е разделянето на регистрация и стартиране. Регистрацията се извършва във фазата на инициализация (Activity.onCreate или Fragment.onCreate), callback се свързва с launcher веднъж и гарантирано се задейства при връщане на резултата. Това премахва проблема, когато onActivityResult се задействаше в неочакван ред или върху унищожено Activity.
ActivityResultLauncher поддържа всички сценарии, които преди се обработваха чрез onActivityResult: стартиране на камера, галерия, заявка за контакти, разрешения и персонализирани Activity. Освен това API е разширяем: разработчикът може да създава свои собствени Contract за специфични сценарии за обмен на данни между Activity.
startActivityForResult беше част от Android SDK от версия API Level 1 (2008) и остана основният начин за получаване на резултат от Activity в продължение на повече от 12 години. Въпреки това, този метод имаше фундаментални недостатъци, които Google премахна в Activity Result API. Нека разгледаме основните проблеми и как новият API ги решава.
Методът startActivityForResult е свързан с Activity и Fragment чрез requestCode — произволно цяло число, което се предава на onActivityResult. Разработчикът ръчно съпоставяше кода със стартираната операция, което водеше до грешки при повторно използване на код и наследяване. ActivityResultLauncher напълно премахва requestCode: callback се свързва с конкретен launcher във фазата на регистрация и се извиква само за него.
При конфигурационни промени (завъртане на екрана, смяна на езика) Activity се пресъздаваше и onActivityResult можеше да не се задейства — callback се губеше. Activity Result API автоматично запазва и възстановява състоянието на launcher чрез SavedStateRegistry, което гарантира получаване на резултата дори след пресъздаване на Activity.
Старият API предаваше резултата чрез Intent с Bundle, където ключовете и типовете данни не се проверяваха от компилатора. Activity Result API използва Contract — генеричен интерфейс, който определя типа на входните данни (I) и типа на резултата (O). Грешките от несъответствие на типове се откриват във фазата на компилация, а не по време на изпълнение.
| Характеристика | startActivityForResult | ActivityResultLauncher |
|---|---|---|
| RequestCode | Изисква ръчно управление | Автоматичен, не се изисква |
| Типова безопасност | Не | Generic Contract |
| Запазване при завъртане | Изгубва се | SavedStateRegistry |
| Минимален API | API Level 1 | Activity 1.2.0 |
| Използване в Compose | Не се поддържа | rememberLauncherForActivityResult |
Contract е интерфейс ActivityResultContract<I, O>, който определя как да стартира Activity и как да интерпретира резултата. Google предоставя набор от вградени договори за типични сценарии, покриващи повечето нужди на разработчика.
StartIntentSenderForResult е основният договор за стартиране на IntentSender. Използва се в системни сценарии, например при удостоверяване чрез Google Sign-In или при плащане чрез Google Pay. Входен параметър — PendingIntent, изход — ActivityResult с код и Intent.
RequestMultiplePermissions е договорът за едновременно искане на множество разрешения в Android 6.0+. Входен параметър — масив String с имена на разрешения, изход — Map<String, Boolean> с резултата от всяка заявка. Преди това изискваше ръчно анализиране в onRequestPermissionsResult със съпоставяне на кодове на заявки.
TakePicture е договорът за снимане чрез системната камера. Вход — Uri, където да се запази снимката, изход — Boolean (успех). TakeVideo работи по подобен начин с видео. Тези договори заместват остарелия MediaStore.ACTION_IMAGE_CAPTURE с нестабилно поведение на различни устройства.
GetContent е договорът за избор на съдържание чрез системния избирач. Вход — MIME тип (например image/*), изход — Uri на избрания файл. OpenDocument се различава с поддръжка за множествен избор и филтриране по типове документи. И двата договора работят чрез SAF (Storage Access Framework).
CreateDocument е договорът за създаване на нов документ чрез системния диалогов прозорец. Потребителят избира име и папка, системата връща Uri за запис. OpenDocumentTree предоставя достъп до цяла директория — потребителят избира папка и приложението получава tree-uri за четене и запис на всички файлове вътре.
Основният модел на използване на ActivityResultLauncher в класическия Android се състои от две стъпки: регистрация чрез registerForActivityResult във фазата на инициализация и извикване на launch в отговор на действие на потребителя. Нека разгледаме типичен пример за избор на изображение от галерията.
Регистрирайте launcher в onCreate на Activity — това гарантира, че callback ще бъде готов преди всяко възможно извикване. Никога не регистрирайте launcher непосредствено преди стартиране — това нарушава договора на API и може да доведе до загуба на резултат при пресъздаване на 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/*")
}
}
В Fragment регистрацията се извършва в onCreate, onAttach или инициализация в onCreateView. FragmentActivity предава launcher чрез родителското Activity, така че резултатът се обработва вътре във Fragment, а не в Activity. Това подобрява капсулирането в сравнение с onActivityResult, където всички резултати от всички Fragment се събираха в един метод на Activity.
class ProfileFragment : Fragment() {
private val cameraLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) { updateProfilePhoto() }
}
fun takePhoto(photoUri: Uri) {
cameraLauncher.launch(photoUri)
}
}
Jetpack Compose предоставя специална composable функционалност за Activity Result API — rememberLauncherForActivityResult. За разлика от класическия подход, в Compose launcher се създава като обект, свързан с жизнения цикъл на composable чрез remember. Това позволява използването на Activity Result API в напълно декларативен стил без директен достъп до Activity или Fragment.
rememberLauncherForActivityResult приема Contract и callback, връщайки ActivityResultLauncher. Launcher се запазва при рекомпозиция и автоматично се почиства при излизане от композицията. Извикването на launch се случва в отговор на събитие — например натискане на бутон или промяна на състояние.
@Composable
fun PhotoPicker() {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
Button(onClick = { launcher.launch("image/*") }) {
Text("Изберете снимка")
}
}
Искане на разрешения в Compose също се извършва чрез rememberLauncherForActivityResult с договор RequestPermission или RequestMultiplePermissions. Google препоръчва използването на accompanist-permissions, но под капака той също използва Activity Result API. За контрол на състоянието на разрешенията е удобно да съхранявате състоянието в remember или ViewModel.
Activity Result API премахна много проблеми на стария подход, но неправилното му използване може да доведе до нови видове грешки. Нека разгледаме най-честите проблеми и начините за избягването им.
Регистрацията на launcher трябва да се извърши при инициализация на компонента — в onCreate на Activity или инициализатора на Fragment. Ако регистрирате launcher вътре в ламбда, callback или корутина, при пресъздаване на Activity регистрацията може да бъде извършена отново и старият launcher ще загуби връзка с резултата.
Всеки launcher получава уникален ключ за запазване на състояние. Ако регистрирате два launcher с еднакъв Contract в един компонент, SavedStateRegistry може да презапише състоянието на единия с другия. Android Studio предупреждава за това чрез lint правилото UnnecessaryRegisterForActivityResult, но е по-добре да контролирате уникалността ръчно.
Потребителят може да отмени действието — да натисне системния бутон за назад, да минимизира приложението или да превключи към друго приложение. В този случай callback ще получи null или ActivityResult с RESULT_CANCELED. Винаги проверявайте резултата за null преди използване, за да избегнете NullPointerException.
Ако вашето приложение често стартира подобни сценарии — например избор на контакт с връщане на име и телефон — създайте свой собствен Contract. Това подобрява четимостта на кода и позволява централизирана промяна на логиката за стартиране и обработка на резултата.
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) }
}
Често задавани въпроси
Не — ActivityResultLauncher изисква контекст на Activity или Fragment за регистрация. Използвайте ViewModel само за съхраняване на състояние, а launcher създавайте в Activity или Fragment и предавайте резултата на ViewModel.
Activity Result API е достъпен от библиотеката activity-ktx 1.2.0. Минимален SDK — API Level 14 (Android 4.0), но повечето договори работят само на API Level 19+.
Повторното извикване на launch преди завършване на първата операция ще бъде игнорирано. Activity Result API не поддържа паралелни стартирания — изчакайте callback от първата операция преди ново извикване.
Миграцията се извършва чрез замяна на извикването на startActivityForResult с registerForActivityResult със съответния Contract. Премахнете onActivityResult и обработвайте резултата в callback на launcher. Google предоставя миграционно ръководство в документацията на Android Developers.
Да, много библиотеки поддържат интеграция чрез ActivityResultContracts. Например ML Kit Barcode Scanner използва StartIntentSenderForResult за стартиране на скенера. Проверете документацията на конкретната библиотека.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също