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
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("Choose Photo")
}
}
Запрос разрешений в Compose также выполняется через rememberLauncherForActivityResult с контрактом RequestPermission или RequestMultiplePermissions. Google recommends использовать 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также