'Swift, Firebase, array of map: why are the @DocumentIDs for each Album duplicated in each element of the Song array?
I'm working through Javed Davidson's "Music Player in a Day" YouTube tutorial, but with some notable changes and I'm experiencing something very unexpected; namely, I only see one song for each album.
When I walk through the code, I see that the songs have all been given the same DocumentID, which explains the symptom, but not the cause. (RHPS!)
Here's some code:
struct Album: Identifiable, Codable {
@DocumentID var id: String?
var title: String
var image: String
var releaseDate: Date
var songs: [Song]
}
struct Song: Identifiable, Codable {
@DocumentID var id: String?
var title: String
var timeInSeconds: Int
}
struct ContentView: View {
@EnvironmentObject var mpData: MusicPlayerData
@State private var currentAlbum: Album?
var body: some View {
NavigationView {
ScrollView {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(mpData.albums) { album in
AlbumArtView(album: album)
.onTapGesture { self.currentAlbum = album }
}
}
}
LazyVStack(alignment: .leading) {
if let album = currentAlbum {
ForEach(album.songs) { song in
SongCellView(album: album, song: song)
}
}
}
}
.navigationTitle("Bob Seger")
}
}
}
If I change the ForEach(album.songs) to ForEach(album.songs, id:\.title), then it works.
But, of course, I'd prefer to use the id property of the struct.
Can someone tell me what I'm doing wrong and how to fix it?
Solution 1:[1]
I had a quick look at Jared's video and it seems like he is not using Codable, but instead maps the documents manually, so I assume using Codable is one of the substantial changes you mention in your question.
The reason why your songs don't get a unique ID is that @DocumentID only works for documents.
In the screenshot we can see that you use Firestore documents for albums, and a nested array for the songs of an album. This means that the albums' document IDs will be mapped to the id attribute of the Album struct.
As a nested array is not a document, it doesn't have a document ID, so all the song ids will be nil, which is why you see only one song per album.
There are couple of ways around this:
- Use the
titleas the identifying attribute (just like you did) - Store all the songs in a top-level collection
songsand add an attributealbumIDthat refers to the album they belong to. This will allow you to fetch all songs belonging to an album using a query. It also allows you to assign a song to multiple albums (which might or might not be what you want). - Use a sub collection for the songs.
How exactly you model your data heavily depends on your use case(s), and there is no one true way of modeling this. I recommend checking out our video series about Firestore to get a deeper understanding of how to model data for Firestore: Get to know Cloud Firestore, in particular Maps, Arrays and Subcollections, Oh My! | Get to know Cloud Firestore #4 - YouTube and How to Structure Your Data | Get to know Cloud Firestore #5 - YouTube.
To learn more about how to map your Firestore documents to/from Swift, check out this comprehensive blog post I wrote. It also contains code snippets that show how to handle mapping errors: Mapping Firestore Data in Swift - The Comprehensive Guide. There is also a fully working sample app that demonstrates all the different ways to map simple data types, custom data types, colors, arrays, dates, geopoints, etc: peterfriese/Swift-Firestore-Guide: The Comprehensive Guide to using Cloud Firestore in Swift.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Peter Friese |

