Swift Firebase Custom Object with Document Id

Swift Firebase Custom Object with Document ID

There is an even easier way to achieve this using the Codable support we added to Firestore recently:

Add the FirebaseFirestoreSwift pod:

# Uncomment the next line to define a global platform for your project
platform :ios, '13.0'

target 'MyAwesomeApp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!

# Pods for MyAwesomeApp
pod 'Firebase/Analytics'
pod 'Firebase/Firestore'
pod 'FirebaseFirestoreSwift'
end
import FirebaseFirestoreSwift
struct City: Codable, Identifiable {
@DocumentID var id: String? = UUID().uuidString
let name: String
let state: String
let country: String
let isCapital: Bool
let population: Int64

// you only need CodingKeys if your attribute names in Firestore differ from the property names on your struct!
}

By using the @DocumentID property wrapper, you tell Firestore's Codable support that you want it to map the document's ID to the id field. Also note that - as the City struct implements Identifiable - you will be able to use it in a SwiftUI ListView.

Then, in your view model, use queryDocumentSnapshot.data(as:) to fetch and map data in a typesafe way:

import FirebaseFirestore

class CitiesViewModel: ObservableObject {
@Published var cities = [City]()

private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?

deinit {
unregister()
}

func unregister() {
if listenerRegistration != nil {
listenerRegistration?.remove()
}
}

func fetchData() {
unregister()
listenerRegistration = db.collection("cities").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}

self.cities = documents.compactMap { queryDocumentSnapshot -> City? in
return try? queryDocumentSnapshot.data(as: City.self)
}
}
}
}

How to convert document to a custom object in Swift 5?

After contacting the firebase team, I found the solution I was looking for. It turns out I have to do import FirebaseFirestoreSwift explicitly instead of just doing import Firebase. The error will disappear after this. (And of course you'll need to add the pod to your podfile first:D)

Assigning firestore data to custom object

You must separate access to documents from access to data within documents (you attempt to do it together). You cannot call getDocuments() on a document or a field within a document, only on a collection. So instead of db.collection("customers/test/specs").getDocuments(), try:

db.collection("customers").getDocuments() { (snapshot, error) in ... }

Then to get data from the documents:

db.collection("customers").getDocuments() { (snapshot, error) in
if let snapshot = snapshot { // lead by unwrapping the snapshot instead of the error
for doc in snapshot.documents { // iterate through the documents
guard let specs = doc.get("specs") as? [[String: Any]] else {
continue // continue loop
}
for s in specs { // iterate through the array of specs
if let specNum = s["SpecNum"] as? String,
let specDesc = s["SpecDesc"] as? String,
let pltCount = s["PalletCount"] as? Int {
let spec = Spec(specNum: specNum, specDesc: specDesc, pltCount: pltCount)
self.someArray.append(spec)
}
}

self.tableView.reloadData() // loop is done, reload
}
} else {
if let error = error {
print(error)
}
}
}

This is a very simplified version of how I imagine you'd actually want to implement it, depending on how the table/collection was reloaded (on the fly, routinely, or just once). Also, each document contains an array of specs but you're fetching all documents from the collection, which would give you a ton of specs without any indication of which spec is tied to which customer. But I suspect this is just early setup and you're just trying to get a handle on the API first.

Note: Maps in Firestore are called dictionaries in Swift and they always come back from Firestore as [String: Any] dictionaries. That's why when we originally unwrapped the specs map, we cast it as an array of dictionaries:

let specs = doc.get("specs") as? [[String: Any]]

Add a Document's Document ID to Its Own Firestore Document - Swift 4

While there is a perfectly fine answer, FireStore has the functionality you need built in, and it doesn't require two calls to the database. In fact, it doesn't require any calls to the database.

Here's an example

    let testRef = self.db.collection("test_node")
let someData = [
"child_key": "child_value"
]

let aDoc = testRef.document() //this creates a document with a documentID
print(aDoc.documentID) //prints the documentID, no database interaction
//you could add the documentID to an object etc at this point
aDoc.setData(someData) //stores the data at that documentID

See the documentation Add a Document for more info.

In some cases, it can be useful to create a document reference with an
auto-generated ID, then use the reference later. For this use case,
you can call doc():

You may want to consider a slightly different approach. You can obtain the document ID in the closure following the write as well. So let's give you a cool Ride (class)

class RideClass {
var availableSeats: Int
var carType: String
var dateCreated: String
var ID: String

init(seats: Int, car: String, createdDate: String) {
self.availableSeats = seats
self.carType = car
self.dateCreated = createdDate
self.ID = ""
}

func getRideDict() -> [String: Any] {
let dict:[String: Any] = [
"availableSeats": self.availableSeats,
"carType": self.carType,
"dateCreated": self.dateCreated
]
return dict
}
}

and then some code to create a ride, write it out and leverage it's auto-created documentID

    var aRide = RideClass(seats: 3, car: "Lincoln", createdDate: "20190122")

var ref: DocumentReference? = nil
ref = db.collection("rides").addDocument(data: aRide.getRideDict() ) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
aRide.ID = ref!.documentID
print(aRide.ID) //now you can work with the ride and know it's ID
}
}

How can I get the document ID when using Codable in Firestore?

You can use Firestore's Codable support to map document IDs. No need to implement a custom decoder - we've done all the hard work for you.

Here is how.

1. Create a model for your data

You already did this. Looking at the attributes in your MoodRecord struct, I assume you want to use date and time to track timestamps, and mood to capture the value of an enum. I've updated the struct accordingly:

struct MoodRecord: Codable, Hashable, Identifiable {
@DocumentID var id: String?
var user: String
var date: Date
var time: Date
var mood: Mood
}

enum Mood: String, Codable {
case great
case ok
case good
case bad
case terrible
}

2. Map data using Codable

Fetching Firestore documents and mapping them to Swift structs becomes a one-liner thanks to Codable:

docRef.getDocument(as: MoodRecord.self) { result in
// handle result
}

Here is a complete code snippet for fetching a single document:

private func fetchMoodRecord(documentId: String) {
let docRef = db.collection("moodrecords").document(documentId)

docRef.getDocument(as: MoodRecord.self) { result in
switch result {
case .success(let moodRecord):
// A MoodRecord value was successfully initialized from the DocumentSnapshot.
self.moodRecord = moodRecord
self.errorMessage = nil
case .failure(let error):
// A MoodRecord value could not be initialized from the DocumentSnapshot.
self.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
}
}

3. Updating a document

To update a document using Codable, use the following code snippet:

func updateMoodRecord(moodRecord: MoodRecord) {
if let id = moodRecord.id {
let docRef = db.collection("moodRecords").document(id)
do {
try docRef.setData(from: moodRecord)
}
catch {
print(error)
}
}
}

4.Adding new documents

Adding new documents is even easier:

func addMoodRecord(moodRecord: MoodRecord) {
let collectionRef = db.collection("moodRecords")
do {
let newDocReference = try collectionRef.addDocument(from: moodRecord)
print("Mood record stored with new document reference: \(newDocReference)")
}
catch {
print(error)
}
}

More

To learn more about how to map Firestore documents using Swift's Codable protocol, including how to map advanced data types such as date, time, colors, enums, how to fetch data using snapshot listeners, and how to handle any errors that might occur during the mapping process, check out Mapping Firestore Data in Swift - The Comprehensive Guide and the accompanying sample project on GitHub



Related Topics



Leave a reply



Submit