Popular Searches
Popular Course Categories
Popular Courses

Selecting Images and Files Using System Pickers

Selecting Images and Files Using System Pickers

Authentication, Permissions, and Device Features

Selecting Images and Files Using System Pickers in Android

Course: Android App Development

Topic: Selecting Images and Files Using System Pickers

Course Link: Android App Development Course

Demo Link: Register for a Free Course Demo

Academy Website: JustAcademy


1. Introduction to System Pickers in Android

Android system pickers allow users to select images, videos, PDFs, documents, and other files from their device or supported storage providers. Instead of building a custom file browser or requesting broad access to the device's storage, an application can launch a system-provided interface and receive access to the items selected by the user.

System pickers are commonly used in profile-photo selection, document uploads, chat attachments, identity verification, image editing, and file-sharing applications.

Android provides different picker APIs depending on the type of content and the access requirements. The Photo Picker is suitable for selecting images and videos, while the Storage Access Framework (SAF) supports selecting general documents and files.

2. Learning Objectives

  • Understand Android system pickers and their advantages.
  • Differentiate between the Photo Picker and the Storage Access Framework.
  • Launch a picker using modern Activity Result APIs.
  • Select one or multiple images and files.
  • Handle the returned content URI safely.
  • Display selected images and read file contents.
  • Understand MIME types, URI permissions, and error handling.
  • Apply privacy and performance best practices.

3. What Is a System Picker?

A system picker is a user interface provided by Android or a compatible document provider. It lets users browse available content and explicitly choose what an application can access.

When the user selects an item, the application generally receives a URI that identifies the selected content. The application can use this URI with Android APIs such as ContentResolver to read the content when permission is available.

Important Concepts

Concept Description
System Picker A standard interface for choosing images, videos, or files.
URI An identifier that points to selected content, often beginning with content://.
MIME Type Describes a content type, such as image/jpeg or application/pdf.
ContentResolver An Android API used to access content provided through content URIs.
Activity Result API A modern API for launching an activity or picker and receiving its result.
Storage Access Framework An Android framework for selecting and accessing documents and files through document providers.

4. Types of Android System Pickers

4.1 Android Photo Picker

The Photo Picker provides a system interface for choosing photos and videos. It lets users share only the media they select instead of granting the application access to their entire media library. Android can use a system implementation or an appropriate fallback depending on device support.

Use the Photo Picker when the application needs selected visual media, such as a profile picture, a post image, or a video attachment.

4.2 Storage Access Framework (SAF)

The Storage Access Framework lets users select general files, including PDFs, text files, spreadsheets, and other documents. It can expose content from local storage, cloud storage providers, and other compatible document providers.

Use SAF when the application needs a document or file rather than only a photo or video. The framework includes actions such as ACTION_OPEN_DOCUMENT and ACTION_CREATE_DOCUMENT.

4.3 Choosing the Correct Picker

Requirement Recommended API Typical Use
Select a photo or video Photo Picker / PickVisualMedia Profile photo, image post, video attachment
Select multiple photos or videos PickMultipleVisualMedia Gallery upload or multiple attachments
Select a PDF or document OpenDocument Resume upload, invoice import
Select multiple general files OpenMultipleDocuments Multiple document attachments
Choose a save location CreateDocument Export a report or save a PDF
Select a directory ACTION_OPEN_DOCUMENT_TREE Apps that manage user-selected folders

5. Using the Android Photo Picker

The modern AndroidX Activity Result API provides contracts for launching the Photo Picker. These contracts help manage the picker launch and result without manually handling request codes in onActivityResult().

5.1 Add the Required Imports

In your Kotlin activity or fragment, import the following classes:

import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity

5.2 Create a Photo Picker Launcher

The following example launches the Photo Picker and displays the selected image in an ImageView.

class ProfileActivity : AppCompatActivity() {

    private lateinit var profileImage: ImageView

    private val pickImage =
        registerForActivityResult(
            ActivityResultContracts.PickVisualMedia()
        ) { uri: Uri? ->
            if (uri != null) {
                profileImage.setImageURI(uri)
            } else {
                // The user cancelled the picker.
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_profile)

        profileImage = findViewById(R.id.profileImage)

        val selectButton =
            findViewById<Button>(R.id.selectImageButton)

        selectButton.setOnClickListener {
            pickImage.launch(
                PickVisualMediaRequest(
                    ActivityResultContracts
                        .PickVisualMedia
                        .ImageOnly
                )
            )
        }
    }
}

How it works:

  1. The Activity Result launcher is registered before it is used.
  2. The user taps the image-selection button.
  3. The application launches the system Photo Picker.
  4. The user selects an image or cancels the operation.
  5. The callback receives a URI or null.
  6. The selected image is displayed using the ImageView.

Important: This example assumes that the activity layout contains an ImageView with the ID profileImage and a Button with the ID selectImageButton.

6. Selecting Videos with the Photo Picker

The Photo Picker can also be configured to select videos. This is useful for messaging applications, social media applications, and video-upload workflows.

private val pickVideo =
    registerForActivityResult(
        ActivityResultContracts.PickVisualMedia()
    ) { uri: Uri? ->
        if (uri != null) {
            // Pass the URI to a video player or upload workflow.
            selectedVideoUri = uri
        } else {
            // The user cancelled the picker.
        }
    }

private var selectedVideoUri: Uri? = null

private fun selectVideo() {
    pickVideo.launch(
        PickVisualMediaRequest(
            ActivityResultContracts
                .PickVisualMedia
                .VideoOnly
        )
    )
}

Store the returned URI in an appropriate state holder and pass it to a video playback component or file-upload service as required. Do not assume that a URI is a direct filesystem path.

7. Selecting Multiple Images

Some applications allow users to choose several images at once. For example, a property-listing application may allow users to upload multiple photographs of a property.

import android.net.Uri
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts

private val pickMultipleImages =
    registerForActivityResult(
        ActivityResultContracts.PickMultipleVisualMedia(10)
    ) { uris: List<Uri> ->
        if (uris.isNotEmpty()) {
            // Update the UI with the selected image list.
            selectedImageUris = uris
        } else {
            // No images were selected.
        }
    }

private var selectedImageUris: List<Uri> = emptyList()

private fun selectMultipleImages() {
    pickMultipleImages.launch(
        PickVisualMediaRequest(
            ActivityResultContracts
                .PickVisualMedia
                .ImageOnly
        )
    )
}

The number passed to PickMultipleVisualMedia represents the requested selection limit. The actual selection behavior can depend on the Android version and picker implementation, so validate the returned list in your application.

8. Selecting Documents Using the Storage Access Framework

When an application needs a PDF, Word document, spreadsheet, or other general file, use the document picker rather than treating every file as an image.

8.1 Select a PDF File

import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts

private val openPdf =
    registerForActivityResult(
        ActivityResultContracts.OpenDocument()
    ) { uri: Uri? ->
        if (uri != null) {
            selectedPdfUri = uri
            // Read the PDF or pass it to another component.
        } else {
            // The user cancelled the picker.
        }
    }

private var selectedPdfUri: Uri? = null

private fun selectPdf() {
    openPdf.launch(arrayOf("application/pdf"))
}

The MIME type application/pdf filters the selection to PDF documents. The system picker may show documents from different providers, depending on what is available on the device.

8.2 Select Different File Types

You can pass one or more MIME types to OpenDocument. For example, the following launcher accepts PDF and plain-text files.

private val openDocuments =
    registerForActivityResult(
        ActivityResultContracts.OpenDocument()
    ) { uri: Uri? ->
        if (uri != null) {
            selectedDocumentUri = uri
        }
    }

private var selectedDocumentUri: Uri? = null

private fun selectDocument() {
    openDocuments.launch(
        arrayOf("application/pdf", "text/plain")
    )
}

Use accurate MIME types to make the picker easier to use. If the application accepts any file type, use a suitable wildcard such as */* only when that behavior is genuinely required.

8.3 Select Multiple Documents

private val openMultipleDocuments =
    registerForActivityResult(
        ActivityResultContracts.OpenMultipleDocuments()
    ) { uris: List<Uri> ->
        if (uris.isNotEmpty()) {
            selectedDocumentUris = uris
        } else {
            // The user cancelled the selection.
        }
    }

private var selectedDocumentUris: List<Uri> = emptyList()

private fun selectDocuments() {
    openMultipleDocuments.launch(
        arrayOf("application/pdf", "text/plain")
    )
}

After selection, validate every URI and apply the application's file-count and file-size limits before processing or uploading the files.

9. Understanding Content URIs

A selected item is commonly represented by a URI such as:

content://com.example.provider/documents/123

This URI identifies content made available through a provider. It is not necessarily the physical path of a file on the device.

Why You Should Not Convert a URI Directly into a File Path

  • The content may be stored in a cloud provider rather than local storage.
  • The provider may not expose a conventional filesystem path.
  • Some providers generate content dynamically or provide access through a stream.
  • Hard-coded path assumptions can fail across devices and Android versions.

Use ContentResolver to open the selected content. If another component requires a physical file, copy the stream into an application-controlled temporary file when appropriate.

10. Reading a Selected File with ContentResolver

The following example reads a text document using its URI. The operation is performed on a background dispatcher to avoid blocking the main thread.

import android.content.ContentResolver
import android.net.Uri
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

suspend fun readTextFile(
    contentResolver: ContentResolver,
    uri: Uri
): String? = withContext(Dispatchers.IO) {
    contentResolver.openInputStream(uri)?.bufferedReader()
        ?.use { reader ->
            reader.readText()
        }
}

The use block closes the reader and underlying stream even if an exception occurs. For large documents, avoid loading the entire content into memory; process the stream incrementally instead.

11. Getting File Metadata

An application may need a selected file's display name, MIME type, or size before uploading or processing it.

import android.content.ContentResolver
import android.database.Cursor
import android.net.Uri
import android.provider.OpenableColumns

data class SelectedFileInfo(
    val displayName: String?,
    val sizeBytes: Long?,
    val mimeType: String?
)

fun getSelectedFileInfo(
    resolver: ContentResolver,
    uri: Uri
): SelectedFileInfo {
    var displayName: String? = null
    var sizeBytes: Long? = null

    val cursor: Cursor? = resolver.query(
        uri,
        arrayOf(
            OpenableColumns.DISPLAY_NAME,
            OpenableColumns.SIZE
        ),
        null,
        null,
        null
    )

    cursor?.use {
        if (it.moveToFirst()) {
            val nameIndex = it.getColumnIndex(
                OpenableColumns.DISPLAY_NAME
            )
            val sizeIndex = it.getColumnIndex(
                OpenableColumns.SIZE
            )

            if (nameIndex >= 0) {
                displayName = it.getString(nameIndex)
            }

            if (sizeIndex >= 0 && !it.isNull(sizeIndex)) {
                sizeBytes = it.getLong(sizeIndex)
            }
        }
    }

    val mimeType = resolver.getType(uri)

    return SelectedFileInfo(
        displayName = displayName,
        sizeBytes = sizeBytes,
        mimeType = mimeType
    )
}

Some providers may not supply every metadata field. A missing size should not automatically be interpreted as a zero-byte file. Handle unknown metadata safely and enforce any necessary limits while reading the content.

12. Displaying a Selected Image

For a simple screen, an ImageView can display an image from a URI:

imageView.setImageURI(selectedImageUri)

For production applications that display many images or large images, use an image-loading library such as Coil or Glide. These libraries help with asynchronous loading, caching, resizing, and memory management.

Always consider image dimensions and memory usage. Loading a very large image directly into a small thumbnail can consume unnecessary memory.

13. Copying a Selected File into App Storage

Sometimes an application needs its own copy of a selected file. For example, an app may need to process an attachment later without relying on a provider's URI remaining available.

import android.content.ContentResolver
import android.net.Uri
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.IOException

suspend fun copyToCache(
    context: Context,
    uri: Uri
): File? = withContext(Dispatchers.IO) {
    val destination = File(
        context.cacheDir,
        "selected_${System.currentTimeMillis()}"
    )

    try {
        val input = context.contentResolver.openInputStream(uri)
            ?: return@withContext null

        input.use { source ->
            destination.outputStream().use { output ->
                source.copyTo(output)
            }
        }

        destination
    } catch (e: IOException) {
        destination.delete()
        null
    }
}

This example copies the file to the app's cache directory. Cache files can be removed by the system, so do not use cache storage for content that must be preserved permanently. For persistent app-owned content, choose an appropriate files directory and define a cleanup policy.

In production code, use a meaningful safe filename and extension when needed, prevent name collisions, enforce a maximum file size, and handle security or provider-related exceptions. Do not trust a user-provided filename as a safe filesystem path.

14. URI Permissions and Persistent Access

System pickers grant access to the selected content according to the API used. Some picker contracts provide temporary access, while the Storage Access Framework can support persistable URI permissions.

If the app must reopen a selected document after the activity or device restarts, use an API that supports persistent access and explicitly retain the permission when it is offered.

// Example for a URI returned by ACTION_OPEN_DOCUMENT.
// Preserve only the permission modes actually granted.
val takeFlags = resultIntentFlags and (
    Intent.FLAG_GRANT_READ_URI_PERMISSION or
    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)

contentResolver.takePersistableUriPermission(
    selectedUri,
    takeFlags
)

This is a conceptual example for handling the result of ACTION_OPEN_DOCUMENT. The result intent flags must be captured from the actual result. Do not assume every picker API or every returned URI supports persistent permission. Release persisted permissions when they are no longer needed.

15. Do System Pickers Require Storage Permissions?

For typical user-selected content, the Photo Picker and Storage Access Framework allow access to selected items without requesting broad storage access. This improves privacy and reduces permission friction.

Do not automatically add READ_EXTERNAL_STORAGE, READ_MEDIA_IMAGES, or READ_MEDIA_VIDEO just because the user selects an image through the Photo Picker. Additional permissions may be required for other application features that independently browse the device's media library, depending on the Android version and intended behavior.

Approach Permission Consideration
Photo Picker Normally no broad media-library permission is needed to access user-selected media.
Storage Access Framework User-selected documents are accessed through granted URI permissions.
App-specific internal storage Normally accessible to the owning app without broad shared-storage permission.
Direct broad media-library access May require runtime permissions depending on Android version and the feature.

16. Handling Cancellation and Errors

Users can close a picker without selecting anything. A null URI or an empty URI list normally means that no item was selected, not that the application has crashed.

Common Situations

  • Picker cancelled: Keep the current screen state unchanged or show a neutral message.
  • URI cannot be opened: The content may have moved, become unavailable, or no longer be accessible.
  • Unsupported file type: Validate the selected content before processing.
  • File too large: Reject it with a clear explanation before uploading or loading it into memory.
  • Provider error: Catch relevant I/O and security exceptions and allow the user to retry.
  • Missing metadata: Handle unknown file names, MIME types, and sizes.
  • Network-backed content: A cloud provider may need time or network connectivity to supply the selected content.

Example of Safe Error Handling

suspend fun safelyReadText(
    resolver: ContentResolver,
    uri: Uri
): Result<String> = withContext(Dispatchers.IO) {
    runCatching {
        resolver.openInputStream(uri)?.bufferedReader()
            ?.use { it.readText() }
            ?: throw IOException("Unable to open selected file")
    }
}

For large files, replace readText() with streaming or incremental parsing to prevent excessive memory use.

17. Security Best Practices

  • Use the narrowest picker that satisfies the application's requirement.
  • Do not assume that a file is safe simply because the system picker returned it.
  • Validate file size, MIME type, and content before processing or uploading.
  • Do not rely only on a filename extension to identify a file's actual contents.
  • Use background dispatchers for file I/O and expensive parsing.
  • Do not log sensitive document contents, authentication information, or private URI data unnecessarily.
  • For uploads, validate content and authorization on the server as well as in the client.
  • Keep temporary copies in app-controlled storage and remove them when no longer needed.
  • Handle unavailable or revoked access gracefully.
  • Use secure transport and appropriate access controls when sending selected files to a backend.

18. Common Mistakes and Troubleshooting

Problem Likely Cause Solution
Selected image is not displayed Invalid URI, unavailable access, or unsupported image content Check the URI, catch loading failures, and test with different providers.
PDF picker shows unexpected files Incorrect or overly broad MIME filter Use application/pdf when selecting PDFs.
App crashes after cancellation Code assumes that the result is never null Handle null URIs and empty lists.
File path is null Content URI does not expose a conventional filesystem path Use ContentResolver streams instead of relying on a path.
Large image causes memory pressure Full-resolution image loaded unnecessarily Use an image loader and request appropriately sized content.
File unavailable after restart Temporary permission or provider access has expired Use a suitable persistent-access workflow or copy the file into app storage.
Selected file cannot be uploaded Provider access, file size, network, or server validation issue Check access, stream the content, enforce limits, and display actionable errors.

19. Practical Example: Profile Image Selection

Consider a profile screen where users can update their profile image. The implementation can follow these steps:

  1. Create an ImageView to display the current profile image.
  2. Add a button labelled Select Image.
  3. Register an Activity Result launcher using PickVisualMedia.
  4. Launch the picker with ImageOnly as the requested media type.
  5. Check whether the returned URI is null.
  6. Display the selected image using an image-loading solution.
  7. If the user confirms the change, upload the selected content using a background task.
  8. Show loading, success, and failure states clearly.
  9. Update the profile record only after the upload workflow succeeds.

This pattern separates selection from uploading. Selecting an image does not automatically upload it or guarantee that the content will remain accessible indefinitely.

20. Practical Example: Resume Upload

A job application application may allow users to select a resume as a PDF. The workflow can be implemented as follows:

  1. Add an Upload Resume button.
  2. Launch OpenDocument with the application/pdf MIME type.
  3. Receive the selected URI in the Activity Result callback.
  4. Read the display name and, if available, the file size.
  5. Apply the app's file-size and content-validation rules.
  6. Read the file through ContentResolver on a background dispatcher.
  7. Upload the content securely to the backend.
  8. Display progress and handle network or server failures.
  9. Store only the metadata and file reference that the application needs.

21. Testing Checklist

  • Test selecting a valid image.
  • Test selecting a video when video selection is supported.
  • Test selecting multiple items.
  • Test choosing a PDF and a text document.
  • Test cancelling the picker.
  • Test selecting content from a cloud provider when available.
  • Test missing metadata and inaccessible URIs.
  • Test large files and large images.
  • Test on different Android versions and devices.
  • Verify that no unnecessary storage permission is requested.
  • Verify that file processing does not block the main thread.
  • Test upload failures and retry behavior.

22. Practice Exercises

  1. Create an Android screen that allows users to select and preview a profile image.
  2. Extend the screen to support selecting up to five images.
  3. Create a document selector that accepts PDF files only.
  4. Display the selected file's name, MIME type, and size when available.
  5. Copy a selected document into app cache storage and read it safely.
  6. Implement an upload screen that validates the selected file and reports upload progress.
  7. Test cancellation and provider errors without crashing the app.

23. Interview Questions and Answers

Q1. What is an Android system picker?

Answer: It is a system-provided interface that lets a user select content, such as a photo, video, or document, for an application to access.

Q2. What is the Android Photo Picker used for?

Answer: It is used to select photos and videos while limiting the application's access to the media chosen by the user.

Q3. What is the Storage Access Framework?

Answer: SAF provides a standard way to select, create, and access documents through compatible document providers.

Q4. What is a content URI?

Answer: A content URI identifies content exposed by a provider. It can be used with ContentResolver to access data when the required permissions are available.

Q5. Why should an app avoid converting every URI into a filesystem path?

Answer: A URI may refer to cloud-hosted or provider-managed content that has no accessible conventional file path. ContentResolver is the appropriate abstraction for reading it.

Q6. What is the Activity Result API?

Answer: It is a modern Android API that lets applications launch activities and receive results through registered contracts and callbacks.

Q7. How can an app select only PDF files?

Answer: Use an OpenDocument contract and pass the MIME type application/pdf.

Q8. What happens when the user cancels a picker?

Answer: The callback may receive null or an empty list, depending on the contract. The application should handle this without crashing.

Q9. Are broad storage permissions always required for picker-based access?

Answer: No. Typical user-selected content access through the Photo Picker or SAF does not require broad storage permissions.

Q10. How should large files be processed?

Answer: Use background I/O, stream the content instead of loading everything into memory, and enforce size limits.

24. Quick Revision Notes

  • Use PickVisualMedia for selecting one photo or video.
  • Use PickMultipleVisualMedia for selecting multiple visual items.
  • Use OpenDocument for general files such as PDFs.
  • Use OpenMultipleDocuments for selecting multiple documents.
  • Use CreateDocument when users need to choose where to save a file.
  • Use ContentResolver to access content URIs.
  • Handle cancellation and missing metadata.
  • Do not assume that a URI represents a local filesystem path.
  • Use background threads or coroutines for file operations.
  • Validate files before processing or uploading them.
  • Request only the permissions required by the actual feature.

25. Conclusion

System pickers are an important part of secure and user-friendly Android development. The Photo Picker is a strong choice for selecting photos and videos, while the Storage Access Framework is suitable for documents and general files. By using Activity Result contracts, handling content URIs correctly, validating selected content, and processing files efficiently, developers can build reliable file-selection workflows without requesting unnecessary access to the device's storage.

26. Official Learning Resources

Explore More: Android App Development Course | Register for a Course Demo | JustAcademy Homepage

Previous Next
whatsapp