Lily Monta
Lily Monta

Reputation: 15

How to Fetch List of Photos and Videos from Device Storage in Android Using Kotlin?

I'm developing an Android app in Kotlin, and I need to retrieve a list of photos and videos from the device storage. I’ve read that MediaStore can be used for this purpose, but I’m unsure how to set up the query to retrieve both images and videos efficiently.

Here’s what I’ve tried so far:

  1. Queried MediaStore.Images.Media and MediaStore.Video.Media separately to get images and videos.
  2. Used a ContentResolver to access the media files.

Fetch photos:

val imageProjection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_ADDED)
val imageCursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageProjection, null, null, null)

Fetch videos:

val videoProjection = arrayOf(MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATE_ADDED)
val videoCursor = contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, videoProjection, null, null, null)

However, I’m not sure if this is the most efficient way or if I should use specific columns to optimize the query.

I want to get a combined list of photos and videos with details like file name, URI, and date added. The solution should handle runtime permissions for accessing external storage.

Upvotes: 0

Views: 131

Answers (2)

Benjamin Lawson
Benjamin Lawson

Reputation: 21

If you want to do separate operations on both then i am providing you exact separate process for photos and videos.

We'll discuss about photos process

1. Handle permission according device below 9 and 10 + (we'll handle for android 13 and 14)

Add Permissions to AndroidManifest.xml For Android 10 and below:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Add Permissions to AndroidManifest.xml For Android 10 +

 <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

2. Ask for permission to user

This will handle permission for both android 10 below and above.

fun checkStoragePermission() {
                val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    Manifest.permission.READ_MEDIA_IMAGES
                } else {
                    Manifest.permission.READ_EXTERNAL_STORAGE
                }
        
                if (ContextCompat.checkSelfPermission(
                        this, permission
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    ActivityCompat.requestPermissions(this, arrayOf(permission), REQUEST_CODE_PERMISSION)
                } else {
                    val photoFolders = getDevicePhotosByFolder(this)  
                    adapter.setDataList(photoFolders)
                }
            }

And handle permission response here

 override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<String>, grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CODE_PERMISSION) {
            if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                val photoFolders = getDevicePhotosByFolder(this)
                adapter.setDataList(photoFolders)
            } else {
                // Permission denied, show a message to the user
                Toast.makeText(this, "Permission denied to access photos", Toast.LENGTH_SHORT)
                    .show()
            }
        }
    }

3. Fetch the photos data

Finally, once the user has granted permission, you may begin using these techniques to instantly retrieve photo data from the user's device.

You would store photo data using this data class to utilize data easily.

data class Photo(
    val uri: String,
    val title: String?,
    val displayName: String?,
    val dateTaken: Long?,
    val dateModified: Long?,
    val size: Long?,
    val width: Int?,
    val height: Int?,
    val mimeType: String?
)

data class PhotoFolder(val folderName: String, val photos: List<Photo>)

This is your code for retrieving data. This will return a List of PhotoFolder objects, which is a list of device photos wrapped in folders, after retrieving all photo data folder-by-folder.

fun getDevicePhotosByFolder(context: Context): List<PhotoFolder> {
    val foldersMap = mutableMapOf<String, MutableList<Photo>>()

    val projection = arrayOf(
        MediaStore.Images.Media._ID,
        MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
        MediaStore.Images.Media.TITLE,
        MediaStore.Images.Media.DISPLAY_NAME,
        MediaStore.Images.Media.DATE_TAKEN,
        MediaStore.Images.Media.DATE_MODIFIED,
        MediaStore.Images.Media.SIZE,
        MediaStore.Images.Media.WIDTH,
        MediaStore.Images.Media.HEIGHT,
        MediaStore.Images.Media.MIME_TYPE
    )

    val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC"

    val cursor: Cursor? = context.contentResolver.query(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, sortOrder
    )

    cursor?.use {
        val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
        val folderColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)
        val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE)
        val displayNameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
        val dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
        val dateModifiedColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED)
        val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
        val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.WIDTH)
        val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.HEIGHT)
        val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)

        while (cursor.moveToNext()) {
            val id = cursor.getLong(idColumn)
            val folderName = cursor.getString(folderColumn)
            val title = cursor.getString(titleColumn)
            val displayName = cursor.getString(displayNameColumn)
            val dateTaken = cursor.getLongOrNull(dateTakenColumn)
            val dateModified = cursor.getLongOrNull(dateModifiedColumn)
            val size = cursor.getLongOrNull(sizeColumn)
            val width = cursor.getIntOrNull(widthColumn)
            val height = cursor.getIntOrNull(heightColumn)
            val mimeType = cursor.getString(mimeTypeColumn)

            val contentUri: Uri = ContentUris.withAppendedId(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id
            )

            val photo = Photo(
                uri = contentUri.toString(),
                title = title,
                displayName = displayName,
                dateTaken = dateTaken,
                dateModified = dateModified,
                size = size,
                width = width,
                height = height,
                mimeType = mimeType
            )

            if (folderName != null) {
                if (!foldersMap.containsKey(folderName)) {
                    foldersMap[folderName] = mutableListOf()
                }
                foldersMap[folderName]?.add(photo)
            }
        }
    }

    return foldersMap.map { (folderName, photos) ->
        PhotoFolder(folderName, photos)
    }
}

fun Cursor.getLongOrNull(columnIndex: Int): Long? =
    if (isNull(columnIndex)) null else getLong(columnIndex)

fun Cursor.getIntOrNull(columnIndex: Int): Int? =
    if (isNull(columnIndex)) null else getInt(columnIndex)

4. Now we'll discuss about retrieving videos from device.

but First you need to handle videos related permissions

Add Permissions to AndroidManifest.xml

 <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

And ask permission from user same as we'd done in photos process.

Data class used for managing video data

data class Video(
    val uri: String,
    val title: String?,
    val displayName: String?,
    val dateTaken: Long?,
    val dateModified: Long?,
    val size: Long?,
    val width: Int?,
    val height: Int?,
    val duration: Long?,
    val mimeType: String?
)

data class VideoFolder(
    val name: String,
    val videos: List<Video>
)

Now we'll retrieve videos data folder-by-folder.

fun getDeviceVideosByFolder(context: Context): List<VideoFolder> {
        val foldersMap = mutableMapOf<String, MutableList<Video>>()

        val projection = arrayOf(
            MediaStore.Video.Media._ID,
            MediaStore.Video.Media.BUCKET_DISPLAY_NAME,
            MediaStore.Video.Media.TITLE,
            MediaStore.Video.Media.DISPLAY_NAME,
            MediaStore.Video.Media.DATE_TAKEN,
            MediaStore.Video.Media.DATE_MODIFIED,
            MediaStore.Video.Media.SIZE,
            MediaStore.Video.Media.WIDTH,
            MediaStore.Video.Media.HEIGHT,
            MediaStore.Video.Media.DURATION,
            MediaStore.Video.Media.MIME_TYPE
        )

        val sortOrder = "${MediaStore.Video.Media.DATE_TAKEN} DESC"

        val cursor: Cursor? = context.contentResolver.query(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, null, null, sortOrder
        )

        cursor?.use {
            val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
            val folderColumn =
                cursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME)
            val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE)
            val displayNameColumn =
                cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
            val dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_TAKEN)
            val dateModifiedColumn =
                cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_MODIFIED)
            val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
            val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.WIDTH)
            val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.HEIGHT)
            val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
            val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.MIME_TYPE)

            while (cursor.moveToNext()) {
                val id = cursor.getLong(idColumn)
                val folderName = cursor.getString(folderColumn)
                val title = cursor.getString(titleColumn)
                val displayName = cursor.getString(displayNameColumn)
                val dateTaken = cursor.getLongOrNull(dateTakenColumn)
                val dateModified = cursor.getLongOrNull(dateModifiedColumn)
                val size = cursor.getLongOrNull(sizeColumn)
                val width = cursor.getIntOrNull(widthColumn)
                val height = cursor.getIntOrNull(heightColumn)
                val duration = cursor.getLongOrNull(durationColumn)
                val mimeType = cursor.getString(mimeTypeColumn)

                val contentUri: Uri = ContentUris.withAppendedId(
                    MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id
                )

                val video = Video(
                    uri = contentUri.toString(),
                    title = title,
                    displayName = displayName,
                    dateTaken = dateTaken,
                    dateModified = dateModified,
                    size = size,
                    width = width,
                    height = height,
                    duration = duration,
                    mimeType = mimeType
                )

                if (folderName != null) {
                    if (!foldersMap.containsKey(folderName)) {
                        foldersMap[folderName] = mutableListOf()
                    }
                    foldersMap[folderName]?.add(video)
                }
            }
        }

        return foldersMap.map { (folderName, videos) ->
            VideoFolder(folderName, videos)
        }
    }

    fun Cursor.getLongOrNull(columnIndex: Int): Long? =
        if (isNull(columnIndex)) null else getLong(columnIndex)

    fun Cursor.getIntOrNull(columnIndex: Int): Int? =
        if (isNull(columnIndex)) null else getInt(columnIndex)

Upvotes: 0

HaxOfficial
HaxOfficial

Reputation: 130

To efficiently retrieve a combined list of photos and videos from the device storage using MediaStore in Kotlin, you can set up a query that targets both image and video content types. By using the MediaStore.Files table, you can query both photos and videos in a single query, which is more efficient than querying them separately.

I'll provide you with a complete solution that includes:

1 - Requesting the necessary runtime permissions.

2 - Querying MediaStore for both images and videos.

3 - Retrieving details like file name, URI, and date added.

Add Permissions to AndroidManifest.xml For Android 10 and below:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

For Android 11 (API level 30) and above, you don’t need additional permissions if you're using MediaStore to access media files (photos and videos).

Handle Permissions in Kotlin Use the READ_EXTERNAL_STORAGE permission for Android 10 and below:

private val storagePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
    if (isGranted) {
        fetchMediaFiles()
    } else {
        Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
    }
}

private fun checkStoragePermission() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // Android 9 and below
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            fetchMediaFiles()
        } else {
            storagePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
        }
    } else {
        // No runtime permission needed for MediaStore access on Android 10+
        fetchMediaFiles()
    }
}

Querying MediaStore for Photos and Videos

fun fetchMediaFiles(context: Context): List<MediaFile> {
    val mediaList = mutableListOf<MediaFile>()

    val projection = arrayOf(
        MediaStore.Files.FileColumns._ID,
        MediaStore.Files.FileColumns.DISPLAY_NAME,
        MediaStore.Files.FileColumns.MIME_TYPE,
        MediaStore.Files.FileColumns.DATE_ADDED
    )

    // Selection for images and videos only
    val selection = "${MediaStore.Files.FileColumns.MEDIA_TYPE}=? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE}=?"
    val selectionArgs = arrayOf(
        MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
        MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()
    )

    val sortOrder = "${MediaStore.Files.FileColumns.DATE_ADDED} DESC"

    val queryUri = MediaStore.Files.getContentUri("external")

    val cursor = context.contentResolver.query(
        queryUri,
        projection,
        selection,
        selectionArgs,
        sortOrder
    )

    cursor?.use {
        val idColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
        val nameColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
        val mimeTypeColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE)
        val dateAddedColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_ADDED)

        while (it.moveToNext()) {
            val id = it.getLong(idColumn)
            val name = it.getString(nameColumn)
            val mimeType = it.getString(mimeTypeColumn)
            val dateAdded = it.getLong(dateAddedColumn)
            
            val contentUri: Uri = ContentUris.withAppendedId(queryUri, id)

            mediaList.add(MediaFile(id, name, contentUri, dateAdded, mimeType))
        }
    }
    return mediaList
}

Upvotes: 0

Related Questions