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 — 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 — уговор за избор садржаја кроз системски бирач. Улаз — 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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође