If an app allows people to create a playlist and add more songs to that created playlist, it would make sense to guard them from accidentally adding the same song to the playlist more than once.
In this code, even though it is successfully receiving the existing playlist from the request, its tracks and entries always show as nil even when there are songs in the playlist.
Any suggestions for how to guard against adding duplicates to a playlist? Thank you!
var request = MusicLibraryRequest<Playlist>()
request.filter(matching: \.name, equalTo: "AppGeneratedPlaylist")
let response = try await request.response()
if let existingPlaylist = response.items.first {
if let tracks = existingPlaylist.entries, tracks.contains(where: { $0.id == song.id }) {
print("Song is already in the playlist, so don't add again")
return
} else {
try await MusicLibrary.shared.add(song, to: existingPlaylist)
print("Added song to existing playlist: \(existingPlaylist.name)")
print("Count of tracks: \(existingPlaylist.tracks?.count)")
print("Count of entries: \(existingPlaylist.entries?.count)")
print("Current tracks: \(existingPlaylist.tracks?.map(\.id))")
print("Current entries: \(existingPlaylist.entries?.map(\.id))")
}
}
Thanks for posting the code. Both print nil because entries and tracks are relationship properties, and a MusicLibraryRequest does not populate them. That is also why the duplicate check never fires. existingPlaylist.entries is nil, so the if let fails and the code always takes the else branch.
Relationships load through with(_:), documented as loading "a new instance of the music item that includes the specified properties". It returns a new value rather than changing the original.
var request = MusicLibraryRequest<Playlist>()
request.filter(matching: \.name, equalTo: "AppGeneratedPlaylist")
let response = try await request.response()
guard let found = response.items.first else { return }
// The request does not populate relationships, so load the one you need.
let playlist = try await found.with([.tracks])
if playlist.tracks?.contains(where: { $0.id == song.id }) == true {
print("Song is already in the playlist")
return
}
let updated = try await MusicLibrary.shared.add(song, to: playlist)
print("Count of tracks: \(updated.tracks?.count ?? 0)")
I compared against tracks rather than entries for two reasons. Playlist.Entry has its own identifier, documented as "the unique identifier for the playlist entry", and that is not the song's identifier. The song itself sits at entry.item. Testing on iOS, a track's identifier does match the song's identifier. tracks also goes back to iOS 15, where entries requires iOS 16.
There is a second reason your prints showed nil. add(_:to:) returns the updated playlist, and it is marked @discardableResult, so nothing warns you when that value is dropped. The existingPlaylist you still hold is the snapshot from before the add. On iOS the returned playlist came back with tracks already loaded and entries still nil.
Asking for one relationship does not populate the others, so loading .tracks leaves entries at nil. That was the same in my testing on iOS and on macOS.