Get a result from an activity
Stay organized with collections
Save and categorize content based on your preferences.
Starting another activity, whether it is one within your app or from another app, doesn't need to be a one-way operation. You can also start an activity and receive a result back. For example, your app can start a camera app and receive the captured photo as a result. Or you might start the Contacts app for the user to select a contact, and then receive the contact details as a result.
While the underlying
startActivityForResult()
and
onActivityResult()
APIs are available on the Activity class on all API levels, Google strongly
recommends using the Activity Result APIs introduced in AndroidX
Activity
and Fragment classes.
The Activity Result APIs provide components for registering for a result, launching the activity that produces the result, and handling the result once it is dispatched by the system.
Register a callback for an activity result
When starting an activity for a result, it is possible—and, in cases of memory-intensive operations such as camera usage, almost certain—that your process and your activity will be destroyed due to low memory.
For this reason, the Activity Result APIs decouple the result callback from the place in your code where you launch the other activity. Because the result callback needs to be available when your process and activity are recreated, the callback must be unconditionally registered every time your activity is created, even if the logic of launching the other activity only happens based on user input or other business logic.
When in a
ComponentActivity or a
Fragment, the Activity Result
APIs provide a
registerForActivityResult()
API for registering the result callback. registerForActivityResult() takes an
ActivityResultContract
and an
ActivityResultCallback
and returns an
ActivityResultLauncher,
which you use to launch the other activity.
An ActivityResultContract defines the input type needed to produce a result
along with the output type of the result. The APIs provide
default contracts
for basic intent actions like taking a picture, requesting permissions, and so
on. You can also
create a custom contract.
ActivityResultCallback is a single method interface with an
onActivityResult()
method that takes an object of the output type defined in the
ActivityResultContract:
Kotlin
valgetContent=registerForActivityResult(GetContent()){uri:Uri? -> // Handle the returned Uri }
Java
// GetContent creates an ActivityResultLauncher<String> to let you pass // in the mime type you want to let the user select ActivityResultLauncher<String>mGetContent=registerForActivityResult(newGetContent(), newActivityResultCallback<Uri>(){ @Override publicvoidonActivityResult(Uriuri){ // Handle the returned Uri } });
If you have multiple activity result calls and you either use different
contracts
or want separate callbacks, you can call registerForActivityResult() multiple
times to register multiple ActivityResultLauncher instances. You must
call registerForActivityResult() in the same order for each creation of your
fragment or activity so that the inflight results are delivered to the
correct callback.
registerForActivityResult() is safe to call before your fragment or activity
is created, letting it be used directly when declaring member variables
for the returned ActivityResultLauncher instances.
Launch an activity for result
While registerForActivityResult() registers your callback, it does not
launch the other activity and kick off the request for a result. Instead, this
is the responsibility of the returned ActivityResultLauncher instance.
If input exists, the launcher takes the input that matches the type of the
ActivityResultContract. Calling
launch()
starts the process of producing the result. When the user is done with the
subsequent activity and returns, the onActivityResult() from the
ActivityResultCallback is then executed, as shown in the following example:
Kotlin
valgetContent=registerForActivityResult(GetContent()){uri:Uri? -> // Handle the returned Uri } overridefunonCreate(savedInstanceState:Bundle?){ // ... valselectButton=findViewById<Button>(R.id.select_button) selectButton.setOnClickListener{ // Pass in the mime type you want to let the user select // as the input getContent.launch("image/*") } }
Java
ActivityResultLauncher<String>mGetContent=registerForActivityResult(newGetContent(), newActivityResultCallback<Uri>(){ @Override publicvoidonActivityResult(Uriuri){ // Handle the returned Uri } }); @Override publicvoidonCreate(@NullableBundlesavedInstanceState){ // ... ButtonselectButton=findViewById(R.id.select_button); selectButton.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewview){ // Pass in the mime type you want to let the user select // as the input mGetContent.launch("image/*"); } }); }
An overloaded version of
launch()
lets you pass an
ActivityOptionsCompat
in addition to the input.
Receive an activity result in a separate class
While the ComponentActivity and Fragment classes implement the
ActivityResultCaller
interface to let you use the registerForActivityResult() APIs, you can also
receive the activity result in a separate class that doesn't implement
ActivityResultCaller by using
ActivityResultRegistry
directly.
For example, you might want to implement a
LifecycleObserver
that handles registering a contract along with launching the launcher:
Kotlin
classMyLifecycleObserver(privatevalregistry:ActivityResultRegistry) :DefaultLifecycleObserver{ lateinitvargetContent:ActivityResultLauncher<String> overridefunonCreate(owner:LifecycleOwner){ getContent=registry.register("key",owner,GetContent()){uri-> // Handle the returned Uri } } funselectImage(){ getContent.launch("image/*") } } classMyFragment:Fragment(){ lateinitvarobserver:MyLifecycleObserver overridefunonCreate(savedInstanceState:Bundle?){ // ... observer=MyLifecycleObserver(requireActivity().activityResultRegistry) lifecycle.addObserver(observer) } overridefunonViewCreated(view:View,savedInstanceState:Bundle?){ valselectButton=view.findViewById<Button>(R.id.select_button) selectButton.setOnClickListener{ // Open the activity to select an image observer.selectImage() } } }
Java
class MyLifecycleObserverimplementsDefaultLifecycleObserver{ privatefinalActivityResultRegistrymRegistry; privateActivityResultLauncher<String>mGetContent; MyLifecycleObserver(@NonNullActivityResultRegistryregistry){ mRegistry=registry; } publicvoidonCreate(@NonNullLifecycleOwnerowner){ // ... mGetContent=mRegistry.register("key",owner,newGetContent(), newActivityResultCallback<Uri>(){ @Override publicvoidonActivityResult(Uriuri){ // Handle the returned Uri } }); } publicvoidselectImage(){ // Open the activity to select an image mGetContent.launch("image/*"); } } class MyFragmentextendsFragment{ privateMyLifecycleObservermObserver; @Override voidonCreate(BundlesavedInstanceState){ // ... mObserver=newMyLifecycleObserver(requireActivity().getActivityResultRegistry()); getLifecycle().addObserver(mObserver); } @Override voidonViewCreated(@NonNullViewview,@NullableBundlesavedInstanceState){ ButtonselectButton=findViewById(R.id.select_button); selectButton.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewview){ mObserver.selectImage(); } }); } }
When using the ActivityResultRegistry APIs, Google strongly recommends using
the APIs that take a LifecycleOwner, as the LifecycleOwner automatically
removes your registered launcher when the Lifecycle is destroyed. However,
in cases where a LifecycleOwner isn't available, each
ActivityResultLauncher class lets you manually call
unregister()
as an alternative.
Test
By default, registerForActivityResult() automatically uses the
ActivityResultRegistry
provided by the activity. It also provides an overload that lets you pass
in your own instance of ActivityResultRegistry that you can use to test your
activity result calls without actually launching another activity.
When testing your app’s fragments, you
provide a test ActivityResultRegistry using a
FragmentFactory to pass
in the ActivityResultRegistry to the fragment’s constructor.
For example, a fragment that uses the TakePicturePreview contract to get a
thumbnail
of the image might be written similar to the following:
Kotlin
classMyFragment( privatevalregistry:ActivityResultRegistry ):Fragment(){ valthumbnailLiveData=MutableLiveData<Bitmap?> valtakePicture=registerForActivityResult(TakePicturePreview(),registry){ bitmap:Bitmap? ->thumbnailLiveData.setValue(bitmap) } // ... }
Java
publicclass MyFragmentextendsFragment{ privatefinalActivityResultRegistrymRegistry; privatefinalMutableLiveData<Bitmap>mThumbnailLiveData=newMutableLiveData(); privatefinalActivityResultLauncher<Void>mTakePicture= registerForActivityResult(newTakePicturePreview(),mRegistry,newActivityResultCallback<Bitmap>(){ @Override publicvoidonActivityResult(Bitmapthumbnail){ mThumbnailLiveData.setValue(thumbnail); } }); publicMyFragment(@NonNullActivityResultRegistryregistry){ super(); mRegistry=registry; } @VisibleForTesting @NonNull ActivityResultLauncher<Void>getTakePicture(){ returnmTakePicture; } @VisibleForTesting @NonNull LiveData<Bitmap>getThumbnailLiveData(){ returnmThumbnailLiveData; } // ... }
When creating a test-specific ActivityResultRegistry, you must implement
the
onLaunch()
method. Instead of calling startActivityForResult(), your test
implementation can call
dispatchResult()
directly, providing the exact results you want to use in your test:
valtestRegistry=object:ActivityResultRegistry(){
overridefun<I,O>onLaunch(
requestCode:Int,
contract:ActivityResultContract<I,O>,
input:I,
options:ActivityOptionsCompat?
){
dispatchResult(requestCode,expectedResult)
}
}
The complete test creates the expected result, constructs a test
ActivityResultRegistry, passes it to the fragment, triggers the launcher
either directly or using other test APIs such as Espresso, and then verifies
the results:
@Test
funactivityResultTest{
// Create an expected result Bitmap
valexpectedResult=Bitmap.createBitmap(1,1,Bitmap.Config.RGBA_F16)
// Create the test ActivityResultRegistry
valtestRegistry=object:ActivityResultRegistry(){
overridefun<I,O>onLaunch(
requestCode:Int,
contract:ActivityResultContract<I,O>,
input:I,
options:ActivityOptionsCompat?
){
dispatchResult(requestCode,expectedResult)
}
}
// Use the launchFragmentInContainer method that takes a
// lambda to construct the Fragment with the testRegistry
with(launchFragmentInContainer{MyFragment(testRegistry)}){
onFragment{fragment->
// Trigger the ActivityResultLauncher
fragment.takePicture()
// Verify the result is set
assertThat(fragment.thumbnailLiveData.value)
.isSameInstanceAs(expectedResult)
}
}
}
Create a custom contract
While ActivityResultContracts
contains a number of prebuilt ActivityResultContract classes for use, you can
provide your own contracts that provide the precise type-safe API you need.
Each ActivityResultContract requires defined input and output classes,
using Void as the input type if you
don't require any input (in Kotlin, use either Void? or Unit).
Each contract must implement the
createIntent()
method, which takes a Context and the input and constructs the Intent that
is used
with startActivityForResult().
Each contract must also implement
parseResult(),
which produces the output from the given resultCode, such as
Activity.RESULT_OK or Activity.RESULT_CANCELED, and the Intent.
Contracts can optionally implement
getSynchronousResult()
if it is possible to determine the result for a given input without
needing to call createIntent(), start the other activity, and use
parseResult() to build the result.
The following example shows how to construct an ActivityResultContract:
Kotlin
classPickRingtone:ActivityResultContract<Int,Uri?>(){ overridefuncreateIntent(context:Context,ringtoneType:Int)= Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply{ putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,ringtoneType) } overridefunparseResult(resultCode:Int,result:Intent?):Uri? { if(resultCode!=Activity.RESULT_OK){ returnnull } returnresult?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) } }
Java
publicclass PickRingtoneextendsActivityResultContract<Integer,Uri>{ @NonNull @Override publicIntentcreateIntent(@NonNullContextcontext,@NonNullIntegerringtoneType){ Intentintent=newIntent(Intent.ACTION_GET_CONTENT); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,ringtoneType.intValue()); returnintent; } @Override publicUriparseResult(intresultCode,@NullableIntentresult){ if(resultCode!=Activity.RESULT_OK||result==null){ returnnull; } returnresult.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); } }
If you don't need a custom contract, you can use the
StartActivityForResult
contract. This is a generic contract that takes any Intent as an input and
returns an
ActivityResult,
letting you extract the resultCode and Intent as part of your callback,
as shown in the following example:
Kotlin
valstartForResult=registerForActivityResult(StartActivityForResult()){result:ActivityResult-> if(result.resultCode==Activity.RESULT_OK){ valintent=result.data // Handle the Intent } } overridefunonCreate(savedInstanceState:Bundle){ // ... valstartButton=findViewById(R.id.start_button) startButton.setOnClickListener{ // Use the Kotlin extension in activity-ktx // passing it the Intent you want to start startForResult.launch(Intent(this,ResultProducingActivity::class.java)) } }
Java
ActivityResultLauncher<Intent>mStartForResult=registerForActivityResult(newStartActivityForResult(), newActivityResultCallback<ActivityResult>(){ @Override publicvoidonActivityResult(ActivityResultresult){ if(result.getResultCode()==Activity.RESULT_OK){ Intentintent=result.getData(); // Handle the Intent } } }); @Override publicvoidonCreate(@NullablesavedInstanceState:Bundle){ // ... ButtonstartButton=findViewById(R.id.start_button); startButton.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewview){ // The launcher with the Intent you want to start mStartForResult.launch(newIntent(this,ResultProducingActivity.class)); } }); }