Skip to content

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

java
// 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

kotlin
// 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

kotlin
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

kotlin
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.)

kotlin
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:

java
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

java
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:

kotlin
// 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:

kotlin
// 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:

kotlin
fun someFunction() {
    // This may crash if called after onCreate
    val launcher = registerForActivityResult(...) 
    launcher.launch(intent)
}

Correct:

kotlin
// 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:

kotlin
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:

  1. Create wrapper classes that maintain compatibility with existing request code patterns
  2. Gradually migrate individual use cases to the new API
  3. Remove wrapper classes once all usages are migrated

Best Practices

  1. Register launchers early - During activity/fragment initialization
  2. Use descriptive names - Clearly indicate each launcher's purpose
  3. Handle null results - Always check for null intent data
  4. Test thoroughly - The new API has different lifecycle considerations
  5. 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.