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), колбек зв’язується з 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 повністю: колбек прив’язується до конкретного launcher на етапі реєстрації та викликається тільки для нього.
При конфігураційних змінах (поворот екрана, зміна мови) Activity перестворювалася, і onActivityResult міг не спрацювати — колбек втрачався. Activity Result API автоматично зберігає та відновлює стан launcher через SavedStateRegistry, що гарантує отримання результату навіть після перестворення Activity.
Старий API передавав результат через Intent з Bundle, де ключі та типи даних не перевірялися компілятором. Activity Result API використовує Contract — generic-інтерфейс, який визначає тип вхідних даних (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 — контракт для вибору контенту через системний picker. Вхід — MIME-тип (наприклад image/*), вихід — Uri вибраного файлу. OpenDocument відрізняється підтримкою множинного вибору та фільтрацією за типами документів. Обидва контракти працюють через SAF (Storage Access Framework).
CreateDocument — контракт для створення нового документа через системний діалог. Користувач вибирає ім’я та папку, система повертає Uri для запису. OpenDocumentTree надає доступ до цілої директорії — користувач вибирає папку, і застосунок отримує tree-uri для читання та запису всіх файлів всередині.
Базовий патерн використання ActivityResultLauncher в класичному Android складається з двох кроків: реєстрація через registerForActivityResult на етапі ініціалізації та виклик launch у відповідь на дію користувача. Розглянемо типовий приклад вибору зображення з галереї.
Реєструйте launcher в onCreate Activity — це гарантує, що колбек буде готовий до будь-якого можливого виклику. Ніколи не реєструйте 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 і колбек, повертаючи 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 всередині лямбди, колбека або корутини, при перестворенні Activity реєстрація може бути виконана повторно, і старий launcher втратить зв’язок з результатом.
Кожен launcher отримує унікальний ключ для збереження стану. Якщо зареєструвати два launcher з однаковим Contract в одному компоненті, SavedStateRegistry може перезаписати стан одного іншим. Android Studio попереджає про це через lint-правило UnnecessaryRegisterForActivityResult, але краще контролювати унікальність вручну.
Користувач може скасувати дію — натиснути системну кнопку назад, згорнути застосунок або переключитися на інший застосунок. У цьому випадку колбек отримає 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 не підтримує паралельні запуски — дочекайтеся колбека від першої операції перед новим викликом.
Міграція виконується заміною виклику startActivityForResult на registerForActivityResult з відповідним Contract. Видаліть onActivityResult і обробляйте результат у колбеку launcher. Google надає міграційний гайд у документації Android Developers.
Так, багато бібліотек підтримують інтеграцію через ActivityResultContracts. Наприклад, ML Kit Barcode Scanner використовує StartIntentSenderForResult для запуску сканера. Перевірте документацію конкретної бібліотеки.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також