Keywords: Android Development | startActivityForResult | Activity Result APIs | Inter-Activity Communication | Type-Safe Contracts
Abstract: This article provides an in-depth exploration of the startActivityForResult mechanism in Android, analyzing its core principles, usage scenarios, and best practices. Through complete code examples, it demonstrates how to launch child activities from the main activity and handle return results, covering both successful and cancelled scenarios. The article also introduces Google's recommended modern alternative - Activity Result APIs, including type-safe contracts, lifecycle-aware callback registration, and custom contract implementation. Testing strategies and performance optimization recommendations are provided to help developers build more robust Android applications.
Fundamental Principles of startActivityForResult
In Android application development, data transfer between activities is a common requirement. startActivityForResult provides a mechanism that allows one activity to start another activity and receive a return result. This pattern is particularly suitable for scenarios that require user input or return data after performing specific operations, such as taking photos, file selection, contact picking, etc.
Detailed Traditional Implementation
Based on the best practices from the Q&A data, we first analyze the traditional startActivityForResult implementation pattern. In the main activity, define a request code and launch the target activity:
// Define request code constant
private static final int LAUNCH_SECOND_ACTIVITY = 1;
// Launch second activity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivityForResult(intent, LAUNCH_SECOND_ACTIVITY);
In the target activity, set different return results based on business logic. When the operation completes successfully:
// Scenario for successful data return
Intent returnIntent = new Intent();
returnIntent.putExtra("video_path", recordedVideoPath);
setResult(Activity.RESULT_OK, returnIntent);
finish();
When the operation fails or the user cancels:
// Scenario for no result return
Intent returnIntent = new Intent();
setResult(Activity.RESULT_CANCELED, returnIntent);
finish();
Override the onActivityResult method in the main activity to handle the return result:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == LAUNCH_SECOND_ACTIVITY) {
if (resultCode == Activity.RESULT_OK) {
String videoPath = data.getStringExtra("video_path");
// Process the successfully returned video path
updateUIWithVideo(videoPath);
} else if (resultCode == Activity.RESULT_CANCELED) {
// Handle cancellation or no result scenarios
handleCancellation();
}
}
}
Modern Activity Result APIs Solution
Google strongly recommends using Activity Result APIs as a modern alternative to startActivityForResult. This solution provides better type safety and lifecycle management.
Basic usage in Kotlin:
// Register result callback
val startForResult = registerForActivityResult(
StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val intent = result.data
// Process the returned Intent data
handleResultData(intent)
}
}
// Launch activity
startForResult.launch(Intent(this, SecondActivity::class.java))
Implementation in Java:
// Declare ActivityResultLauncher
private ActivityResultLauncher<Intent> mStartForResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Register result callback
mStartForResult = registerForActivityResult(
new StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent intent = result.getData();
// Process returned data
handleResultData(intent);
}
}
}
);
// Set button click event
Button startButton = findViewById(R.id.start_button);
startButton.setOnClickListener(view -> {
mStartForResult.launch(new Intent(this, SecondActivity.class));
});
}
Custom Contract Implementation
For specific business requirements, create custom ActivityResultContract to achieve type-safe APIs:
// Custom contract for video selection
class PickVideoContract : ActivityResultContract<Unit, Uri?>() {
override fun createIntent(context: Context, input: Unit): Intent {
return Intent(Intent.ACTION_PICK).apply {
type = "video/*"
}
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
return if (resultCode == Activity.RESULT_OK) {
intent?.data
} else {
null
}
}
}
// Using custom contract
val pickVideo = registerForActivityResult(PickVideoContract()) { uri ->
uri?.let {
// Process selected video URI
processSelectedVideo(it)
}
}
// Launch video selection
pickVideo.launch(Unit)
Lifecycle-Aware Result Handling
Activity Result APIs automatically handle lifecycle changes, ensuring results are correctly received after configuration changes or process recreation:
class MainActivity : AppCompatActivity() {
// Declare at class level to ensure lifecycle safety
private val startForResult = registerForActivityResult(
StartActivityForResult()
) { result ->
// Callback remains valid after activity recreation
if (result.resultCode == Activity.RESULT_OK) {
handleSuccessfulResult(result.data)
}
}
private fun launchSecondActivity() {
startForResult.launch(Intent(this, SecondActivity::class.java))
}
}
Testing Strategies
Writing unit tests for Activity Result APIs:
@Test
fun testActivityResult() {
// Create test registry
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
// Simulate return result
val resultIntent = Intent().apply {
putExtra("test_result", "success")
}
dispatchResult(requestCode, Activity.RESULT_OK, resultIntent)
}
}
// Create fragment using test registry
val fragment = MyFragment(testRegistry)
// Verify result handling
fragment.launchTestActivity()
assertEquals("success", fragment.getResult())
}
Best Practices and Performance Optimization
In actual development, following these best practices can improve code quality and application performance:
Use meaningful request code constants, avoid magic numbers:
companion object {
private const val REQUEST_VIDEO_RECORDING = 1001
private const val REQUEST_IMAGE_PICK = 1002
private const val REQUEST_CONTACT_SELECT = 1003
}
Properly handle memory pressure scenarios, ensuring callbacks can correctly recover when system memory is low:
// Unconditionally register callbacks in onCreate to ensure validity after process recreation
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Register in onCreate even if launch logic depends on user input
videoRecordingLauncher = registerForActivityResult(
StartActivityForResult()
) { result ->
// Handle video recording result
}
}
For complex data transfer, consider using Application class or ViewModel to manage state, avoiding Intent size limitations.