Android Find Albums in Photo Gallery

Sep 24, 2019
with image count and cover image
class Album(val id: String, val name: String, var count: Long = 0, var lastImageUri: Uri? = null)

NOTE: READ_EXTERNAL_STORAGE Permission is required.

Solution 1: Loop all Photos

Loop through all images to find a list of unique albums (bucket).

fun findAlbums(contentResolver: ContentResolver): List<Album> {    val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI    val projections = arrayOf(        MediaStore.Images.ImageColumns._ID,        MediaStore.Images.ImageColumns.BUCKET_ID,        MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,        MediaStore.Images.ImageColumns.DATA    )    val orderBy = "${MediaStore.Images.ImageColumns.DATE_TAKEN} DESC"    val findAlbums = HashMap<String, Album>()    contentResolver.query(contentUri, projections, null, null, orderBy)?.use { cursor ->        if (cursor.moveToFirst()) {            val bucketIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_ID)            val bucketNameIndex =                cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME)            val imageUriIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA)            do {                val bucketId = cursor.getString(bucketIdIndex)                val album = findAlbums[bucketId] ?: let {                    val bucketName = cursor.getString(bucketNameIndex)                    val lastImageUri = Uri.parse(cursor.getString(imageUriIndex))                    val album = Album(                        id = bucketId,                        name = bucketName,                        lastImageUri = lastImageUri                    )                    findAlbums[bucketId] = album                    album                }                album.count++            } while (cursor.moveToNext())        // cursor.close()    }    return findAlbums.values.toList()}

Solution 2: Use Group By

NOTE: GROUP BY statement is not officially documented by ContentResolver.query

NOTE: Tested to work on Android Q (API 29) Emulator

fun findAlbumsWithGroupBy(contentResolver: ContentResolver): List<Album> {    val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI    // display name might not be unique    val projections = arrayOf(        // "DISTINCT ${MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME}"        MediaStore.Images.ImageColumns.BUCKET_ID,        MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,        "COUNT(" + MediaStore.Images.ImageColumns._ID + ") AS image_count"    )    val groupBy = "1) GROUP BY ${MediaStore.Images.ImageColumns.BUCKET_ID}, (${MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME}"    val albums = mutableListOf<Album>()    contentResolver.query(contentUri, projections, groupBy, null, null)?.use { cursor ->        if (cursor.moveToFirst()) {            val bucketIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_ID)            val nameIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME)            val countIndex = cursor.getColumnIndexOrThrow("image_count")            do {                val bucketId = cursor.getString(bucketIdIndex)                val bucketName = cursor.getString(nameIndex)                val imageCount = cursor.getLong(countIndex)                val album = Album(id = bucketId, name = bucketName, count = imageCount)                albums.add(album)                // MAYBE can be optimized further?                val projections = arrayOf(                    MediaStore.Images.ImageColumns._ID,                    MediaStore.Images.ImageColumns.DATA                )                val selection = "${MediaStore.Images.ImageColumns.BUCKET_ID} == ?"                val selectionArgs = arrayOf(                    bucketId                )                val sortOrder = "${MediaStore.Images.ImageColumns.DATE_TAKEN} DESC LIMIT 1"                contentResolver.query(contentUri, projections, selection, selectionArgs, sortOrder)?.use { imageCursor ->                    if (imageCursor.moveToFirst()) {                        val imageUriIndex = imageCursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA)                        val imageUri = imageCursor.getString(imageUriIndex)                        album.lastImageUri = Uri.parse(imageUri)                    }                    // imageCursor.close()                }            } while (cursor.moveToNext())        }        // cursor.close()    }    return albums}

Solution 3: Use Distinct

If you just need a list of image albums name, you could use DISTINCT BUCKET_DISPLAY_NAME.

NOTE: This method is not reliable as there are possibilities of 2 albums (bucket) with the same name.

val projections = arrayOf(    "DISTINCT ${MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME}")

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.