Fix: artists() method to group by album artist with proper URI handling
Problem
The artists() method needs to:
- Group by album artist (with fallback to track artist)
- Generate proper URIs that work with the
artist()method - Handle artist selection correctly
Solution
Replace the artists() method with this implementation that:
- Queries all audios to get album artist information
- Groups by album artist (falling back to track artist)
- Generates URIs based on the artist name hash to ensure consistency
- Works with the existing
artist()method
Implementation
Replace the entire artists() method with:
override fun artists(
providerIdentifier: ProviderIdentifier,
sortingRule: SortingRule,
) = providersManager.flatMapWithInstanceOf(providerIdentifier) {
// Get all audios to extract album artist information
contentResolver.queryFlow(
audiosUri,
audiosProjection,
).mapEachRowToAudio().mapLatest { audios ->
// Group by album artist, falling back to track artist
val artistGroups = audios
.groupBy { audio ->
audio.albumArtist ?: audio.artistName ?: "Unknown Artist"
}
// Sort based on sorting rule
val sortedArtistNames = when (sortingRule.strategy) {
SortingStrategy.NAME -> {
if (sortingRule.reverse) {
artistGroups.keys.sortedDescending()
} else {
artistGroups.keys.sorted()
}
}
else -> artistGroups.keys.sorted()
}
// Create Artist objects with URIs based on artist name hash
val artistsUri = getArtistsUri(volumeName)
sortedArtistNames.map { artistName ->
// Use hash of artist name to generate a consistent URI
val artistId = artistName.hashCode().toLong()
val uri = ContentUris.withAppendedId(artistsUri, artistId)
Artist.Builder(uri)
.setName(artistName)
.build()
}
}.mapLatest {
Result.Success(it)
}
}
Update artist() method
Also update the artist() method to handle the new URI scheme by matching artist name instead of ID:
Replace the artist() method with:
override fun artist(artistUri: Uri) = withVolumeName(artistUri) { volumeName ->
// Get all audios to find matching artist
contentResolver.queryFlow(
getAudiosUri(volumeName),
audiosProjection,
).mapEachRowToAudio(volumeName).mapLatest { audios ->
// Extract the artist name from URI (we'll match by name)
val targetArtistId = ContentUris.parseId(artistUri)
// Find audios matching this artist (by album artist or track artist)
val matchingAudios = audios.filter { audio ->
val artistName = audio.albumArtist ?: audio.artistName ?: "Unknown Artist"
artistName.hashCode().toLong() == targetArtistId
}
if (matchingAudios.isEmpty()) {
return@mapLatest Result.Error<Pair<Artist, ArtistWorks>, Error>(Error.NOT_FOUND)
}
// Get the artist name from first matching audio
val artistName = matchingAudios.first().let { audio ->
audio.albumArtist ?: audio.artistName ?: "Unknown Artist"
}
// Get unique album URIs from matching audios
val albumUris = matchingAudios.map { it.albumUri }.distinct()
// Query albums by URI
contentResolver.queryFlow(
getAlbumsUri(volumeName),
albumsProjection,
bundleOf(
ContentResolver.QUERY_ARG_SQL_SELECTION to query {
MediaStore.Audio.AudioColumns._ID `in` List(albumUris.size) {
Query.ARG
}
},
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS to albumUris.map {
ContentUris.parseId(it).toString()
}.toTypedArray(),
)
).mapEachRowToAlbum(volumeName).mapLatest { albums ->
val artist = Artist.Builder(artistUri)
.setName(artistName)
.build()
val artistWorks = ArtistWorks(
albums,
listOf(),
listOf(),
)
Result.Success(artist to artistWorks)
}
}.flatMapLatest { it }
}
How it works
-
artists() method:
- Queries all audios with album artist information
- Groups by album artist (falls back to track artist)
- Generates URIs using hash of artist name for consistency
- Returns grouped artist list
-
artist() method:
- Receives URI with artist ID (which is hash of artist name)
- Finds all audios matching that artist name
- Queries albums for those audios
- Returns artist details with albums
Testing
After implementing:
- Rebuild APK
- Test artist selection - should now work
- Verify compilation albums are grouped correctly
- Check that artist details show all albums for that artist