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}")