ActivityResult API: Modern Alternative to onActivityResult
Introduction
The onActivityResult()
method has been deprecated in modern Android development, replaced by the more robust and type-safe Activity Result API. This article provides comprehensive guidance on migrating from the deprecated approach to the new API with practical examples and best practices.
Understanding the Deprecation
The traditional onActivityResult()
pattern had several limitations:
- Complex request code management
- Boilerplate code for result handling
- Error-prone manual request code matching
- Lack of type safety
The new Activity Result API addresses these issues with a cleaner, more maintainable approach.
Basic Migration Pattern
Java Implementation
// Register the launcher (typically in onCreate)
ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
// Handle the result
}
}
});
// Launch the activity
Intent intent = new Intent(this, TargetActivity.class);
someActivityResultLauncher.launch(intent);
Kotlin Implementation
// Register the launcher
val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
// Handle the result
}
}
// Launch the activity
val intent = Intent(this, TargetActivity::class.java)
resultLauncher.launch(intent)
Common Use Cases
Requesting Permissions
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Permission granted
} else {
// Permission denied
}
}
// Launch permission request
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
Handling Multiple Permissions
val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions: Map<String, Boolean> ->
permissions.entries.forEach { (permission, isGranted) ->
// Handle each permission result
}
}
// Launch multiple permissions request
requestMultiplePermissions.launch(arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION
))
Picking Content (Images, Files, etc.)
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
// Handle the selected content URI
binding.imageView.setImageURI(uri)
}
}
// Launch content picker
getContent.launch("image/*")
Advanced Implementation Patterns
Generic Activity Launcher Wrapper
For projects needing to maintain compatibility with existing request code patterns:
public class BetterActivityResult<Input, Result> {
private final ActivityResultLauncher<Input> launcher;
private OnActivityResult<Result> onActivityResult;
public interface OnActivityResult<O> {
void onActivityResult(O result);
}
private BetterActivityResult(ActivityResultCaller caller,
ActivityResultContract<Input, Result> contract,
OnActivityResult<Result> onActivityResult) {
this.onActivityResult = onActivityResult;
this.launcher = caller.registerForActivityResult(contract, this::callOnActivityResult);
}
public static <Input, Result> BetterActivityResult<Input, Result> registerForActivityResult(
ActivityResultCaller caller,
ActivityResultContract<Input, Result> contract) {
return new BetterActivityResult<>(caller, contract, null);
}
public void launch(Input input, OnActivityResult<Result> onActivityResult) {
this.onActivityResult = onActivityResult;
launcher.launch(input);
}
private void callOnActivityResult(Result result) {
if (onActivityResult != null) onActivityResult.onActivityResult(result);
}
}
Base Activity Integration
public class BaseActivity extends AppCompatActivity {
protected final BetterActivityResult<Intent, ActivityResult> activityLauncher =
BetterActivityResult.registerActivityForResult(this);
public void startActivityForResult(Intent intent, int requestCode,
ActivityResultCallback<ActivityResult> callback) {
activityLauncher.launch(intent, result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
callback.onActivityResult(requestCode, result.getResultCode(), result.getData());
}
});
}
}
Handling Multiple Request Types
WARNING
The new API eliminates request codes. Each distinct operation requires its own launcher.
Instead of using request codes:
// OLD WAY (deprecated)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_TAKE_PHOTO -> handlePhotoResult(data)
REQUEST_PICK_IMAGE -> handleImagePickResult(data)
}
}
Use separate launchers:
// NEW WAY
val takePhotoLauncher = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
handlePhotoResult(result.data)
}
}
val pickImageLauncher = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
handleImagePickResult(result.data)
}
}
Common Pitfalls and Solutions
Initialization Timing
DANGER
ActivityResultLauncher must be created before the activity reaches STARTED state
Incorrect:
fun someFunction() {
// This may crash if called after onCreate
val launcher = registerForActivityResult(...)
launcher.launch(intent)
}
Correct:
// Initialize in class body or onCreate
val launcher = registerForActivityResult(...)
fun someFunction() {
launcher.launch(intent) // Safe to call anytime
}
Fragment Usage
For fragments, register launchers during initialization:
class MyFragment : Fragment() {
private lateinit var resultLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
// Handle result
}
}
private fun launchActivity() {
val intent = Intent(requireActivity(), TargetActivity::class.java)
resultLauncher.launch(intent)
}
}
Migration Strategy
For large codebases, consider a phased approach:
- Create wrapper classes that maintain compatibility with existing request code patterns
- Gradually migrate individual use cases to the new API
- Remove wrapper classes once all usages are migrated
Best Practices
- Register launchers early - During activity/fragment initialization
- Use descriptive names - Clearly indicate each launcher's purpose
- Handle null results - Always check for null intent data
- Test thoroughly - The new API has different lifecycle considerations
- Use appropriate contracts - Leverage built-in contracts for common operations
Conclusion
The Activity Result API provides a more robust, type-safe alternative to the deprecated onActivityResult()
method. While migration requires some initial effort, the benefits in code clarity, maintainability, and reduced error potential make it well worthwhile. Start with the basic patterns shown here and gradually adapt more complex use cases to fully leverage the new API's capabilities.
TIP
Always test your activity result handling thoroughly after migration, as the timing and lifecycle of the new API differ from the deprecated approach.