Android
Gallery App
Image Selection
Programmatically
Android Development

Get/pick an image from Android's built-in Gallery app programmatically

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Letting users pick an image from their device is one of the most common features in Android apps, from profile picture selection to photo editing. Android has evolved its approach to this task significantly — from raw intents requiring runtime permissions to a modern photo picker that needs no permissions at all. Understanding both the modern and legacy approaches will help you write code that works correctly across API levels while following current best practices.

The Modern Approach: Photo Picker (Android 13+)

Starting with Android 13 (API 33), Google introduced the Photo Picker, a system-provided UI that lets users select photos and videos without granting your app broad storage permissions. This is the recommended approach for new projects:

kotlin
1// Kotlin — using ActivityResultContracts.PickVisualMedia
2class ProfileActivity : AppCompatActivity() {
3
4    // Register the picker launcher
5    private val pickMedia = registerForActivityResult(
6        ActivityResultContracts.PickVisualMedia()
7    ) { uri: Uri? ->
8        if (uri != null) {
9            // User selected an image
10            imageView.setImageURI(uri)
11            handleSelectedImage(uri)
12        } else {
13            // User cancelled the picker
14            Log.d("PhotoPicker", "No media selected")
15        }
16    }
17
18    fun openPhotoPicker() {
19        // Launch the picker, filtering for images only
20        pickMedia.launch(
21            PickVisualMediaRequest(
22                ActivityResultContracts.PickVisualMedia.ImageOnly
23            )
24        )
25    }
26}

For selecting multiple images, use PickMultipleVisualMedia:

kotlin
1private val pickMultipleMedia = registerForActivityResult(
2    ActivityResultContracts.PickMultipleVisualMedia(maxItems = 5)
3) { uris: List<Uri> ->
4    if (uris.isNotEmpty()) {
5        uris.forEach { uri ->
6            Log.d("PhotoPicker", "Selected URI: $uri")
7        }
8    }
9}

The Photo Picker is backported via Google Play Services to devices running Android 4.4 (API 19) and above, so you can use it on most devices in the field today.

Using ActivityResultContracts.GetContent

Before the Photo Picker, the standard modern approach was GetContent, which opens the system file chooser:

kotlin
1class GalleryActivity : AppCompatActivity() {
2
3    private val getContent = registerForActivityResult(
4        ActivityResultContracts.GetContent()
5    ) { uri: Uri? ->
6        uri?.let {
7            imageView.setImageURI(it)
8        }
9    }
10
11    fun selectImage() {
12        // MIME type filter for images
13        getContent.launch("image/*")
14    }
15}

This approach still works and is useful when you need to support file types beyond photos and videos, since GetContent accepts any MIME type.

The Legacy Intent Approach

Before the Activity Result API, developers used startActivityForResult with an explicit intent. You may still encounter this pattern in older codebases:

java
1// Java — legacy approach (deprecated but still functional)
2public class GalleryActivity extends AppCompatActivity {
3
4    private static final int PICK_IMAGE_REQUEST = 1;
5
6    public void openGallery() {
7        Intent intent = new Intent(
8            Intent.ACTION_PICK,
9            MediaStore.Images.Media.EXTERNAL_CONTENT_URI
10        );
11        intent.setType("image/*");
12        startActivityForResult(intent, PICK_IMAGE_REQUEST);
13    }
14
15    @Override
16    protected void onActivityResult(
17        int requestCode, int resultCode, Intent data
18    ) {
19        super.onActivityResult(requestCode, resultCode, data);
20        if (requestCode == PICK_IMAGE_REQUEST
21                && resultCode == RESULT_OK
22                && data != null) {
23            Uri selectedImageUri = data.getData();
24            imageView.setImageURI(selectedImageUri);
25        }
26    }
27}

While startActivityForResult is deprecated, the intents themselves still work. The deprecation is about the callback mechanism, not the underlying capability.

Handling the Returned Uri

The Uri you receive from any picker is a content URI, not a file path. You should work with it through ContentResolver rather than trying to convert it to a file path:

kotlin
1fun loadBitmapFromUri(context: Context, uri: Uri): Bitmap? {
2    return try {
3        context.contentResolver.openInputStream(uri)?.use { stream ->
4            BitmapFactory.decodeStream(stream)
5        }
6    } catch (e: Exception) {
7        Log.e("ImageLoad", "Failed to load image", e)
8        null
9    }
10}
11
12// If you need to copy the image to your app's private storage
13fun copyImageToAppStorage(context: Context, uri: Uri): File? {
14    val inputStream = context.contentResolver.openInputStream(uri)
15        ?: return null
16    val file = File(context.filesDir, "selected_image.jpg")
17    inputStream.use { input ->
18        file.outputStream().use { output ->
19            input.copyTo(output)
20        }
21    }
22    return file
23}

For persisting access to the Uri beyond the current activity lifecycle, you need to take persistable permissions:

kotlin
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags)

Permissions: Old vs New

The permission model for accessing images has changed dramatically across Android versions:

xml
1<!-- AndroidManifest.xml -->
2
3<!-- For Android 12 (API 32) and below -->
4<uses-permission
5    android:name="android.permission.READ_EXTERNAL_STORAGE"
6    android:maxSdkVersion="32" />
7
8<!-- For Android 13 (API 33) and above — granular permissions -->
9<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
10
11<!-- Photo Picker requires NO permissions at all -->

The Photo Picker is the clear winner here because it requires zero permissions. The system handles access scoping internally — your app only gets access to the specific files the user selects, nothing more.

kotlin
1// Checking which permission to request based on API level
2fun getRequiredPermission(): String {
3    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
4        Manifest.permission.READ_MEDIA_IMAGES
5    } else {
6        Manifest.permission.READ_EXTERNAL_STORAGE
7    }
8}

Java Example with GetContent

For teams still writing Java, here is the modern approach without the deprecated startActivityForResult:

java
1public class ProfileActivity extends AppCompatActivity {
2
3    private final ActivityResultLauncher<String> getContent =
4        registerForActivityResult(
5            new ActivityResultContracts.GetContent(),
6            uri -> {
7                if (uri != null) {
8                    imageView.setImageURI(uri);
9                    processSelectedImage(uri);
10                }
11            }
12        );
13
14    public void selectProfileImage() {
15        getContent.launch("image/*");
16    }
17}

Common Pitfalls

  • Trying to get a file path from a content URI: Content URIs are not file paths. Using cursor.getString(cursor.getColumnIndex(DATA)) is unreliable on modern Android — use ContentResolver.openInputStream() instead.
  • Not handling null URIs: The user can cancel the picker at any time, so the returned Uri will be null. Always check for null before processing.
  • Forgetting maxSdkVersion on legacy permissions: Declaring READ_EXTERNAL_STORAGE without maxSdkVersion="32" will trigger unnecessary permission prompts on Android 13+ devices.
  • Assuming the Uri persists across app restarts: Content URIs are temporary by default. Call takePersistableUriPermission() if you need to access the image later, or copy it to your app's private storage.
  • Loading full-resolution bitmaps into memory: A 12MP photo decoded as a full Bitmap can consume 48MB of heap. Use BitmapFactory.Options.inSampleSize or a library like Coil or Glide to load images at the display size.

Summary

  • Use ActivityResultContracts.PickVisualMedia (Photo Picker) for the best user experience with zero permission requirements on Android 13+ and backported to API 19 via Play Services.
  • Use ActivityResultContracts.GetContent when you need to support non-image file types or cannot rely on the Photo Picker backport.
  • Avoid the legacy startActivityForResult pattern in new code — it is deprecated and the Activity Result API is safer and more testable.
  • Always work with content URIs through ContentResolver rather than converting them to file paths.
  • Declare READ_EXTERNAL_STORAGE with maxSdkVersion="32" and use READ_MEDIA_IMAGES for API 33+ to follow the modern permission model.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.