Selecting images from the device gallery is a common requirement in Android applications. However, the old startActivityForResult() method is now deprecated. Modern Android development requires using the Activity Result API along with proper storage handling.
This updated guide on javatechig.com explains how to implement a secure and production-ready image picker using:
- Intent.ACTION_PICK
- Activity Result API
- Scoped Storage (Android 10+)
- Kotlin and Java examples
Understanding Image Picker in Modern Android
In earlier Android versions, developers used:
startActivityForResult()
This is now deprecated.
The recommended approach (API 30+) is:
registerForActivityResult()- Use
ActivityResultContracts.GetContent() - Handle scoped storage correctly
Method 1: Recommended Approach (Activity Result API)
This is the modern and safest way.
Kotlin Implementation
private val imagePickerLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
imageView.setImageURI(it)
}
}
button.setOnClickListener {
imagePickerLauncher.launch("image/*")
}
Explanation:
"image/*"filters only images- No runtime permission required for basic usage
- Works with scoped storage
Java Implementation
ActivityResultLauncher<String> imagePickerLauncher =
registerForActivityResult(new ActivityResultContracts.GetContent(),
uri -> {
if (uri != null) {
imageView.setImageURI(uri);
}
});
button.setOnClickListener(v ->
imagePickerLauncher.launch("image/*"));
Method 2: Using Intent.ACTION_PICK (Legacy-Compatible)
If you need backward compatibility for older apps:
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
startActivity(intent)
⚠ Not recommended for new projects.
Modern apps should migrate to Activity Result API.
Handling Image URI Properly
When you receive the URI:
val inputStream = contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
imageView.setImageBitmap(bitmap)
Always:
- Close input streams
- Avoid loading large images directly into memory
- Use Glide or Coil for production apps
Runtime Permissions (When Required)
For Android 13+:
Use:
READ_MEDIA_IMAGES
For Android 12 and below:
READ_EXTERNAL_STORAGE
However, when using GetContent(), permission is usually not required because it uses a system picker.
Using Photo Picker (Android 13+ Recommended)
Android 13 introduced a system Photo Picker that:
- Does not require storage permission
- Is privacy-safe
- Is recommended by Android platform guidelines
Example:
val pickMedia =
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
imageView.setImageURI(uri)
}
}
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
This is the most future-proof implementation.
Common Errors and Fixes
1. Permission Denied Error
Cause:
- Missing runtime permission (Android <13)
Fix:
- Request permission before accessing storage.
2. Image Not Displaying
Cause:
- Large bitmap loading
- URI not resolved correctly
Fix:
- Use Glide:
Glide.with(this).load(uri).into(imageView)
3. startActivityForResult Deprecated Warning
Cause:
- Using old API
Fix:
- Migrate to Activity Result API.
Best Practices (2026 Updated)
- Prefer ActivityResultContracts.GetContent()
- Use Photo Picker for Android 13+
- Avoid file path extraction from URI
- Do not request unnecessary storage permission
- Use image loading libraries in production
- Handle large images carefully to avoid OOM
Production Checklist
Before shipping your app:
- Confirm scoped storage compliance
- Test on Android 13 and Android 14
- Validate runtime permissions
- Handle null URI safely
- Optimize image loading performance


