user7894007
user7894007

Reputation:

How to map a Firebase document to Swift?

I am able to get all other fields, but I am not sure how to map the field newItems inside the Rooms since it is an array of struct Item.

I have tried let newItem = data["newItems"] as? [Item] ?? [], but this does not seem to be working.

Room.swift

struct Room: Codable, Identifiable {
  var id: String
  var title: String
  var newItems: [Item]
  var members: [String]
}

Item.swift

struct Item: Codable, Identifiable {
  var id: String?
  var name: String
  var desc: String
  var qty: String
  var assignedTo: String
}

Auth.swift

  @Published var rooms = [Room]()

  func populateRoomList () {            
    DispatchQueue.main.async {
      Firestore.firestore().collection("rooms")
        .whereField("members", arrayContains: self.userSession!.uid)
        .addSnapshotListener { snapshot, error in                
          guard let doc = snapshot?.documents else {
            print("No Doc Found")
            return
          }
   
          self.rooms = doc.map({ docSnapshot -> Room in
            let data = docSnapshot.data()
            let docId = docSnapshot.documentID
            let title = data["title"] as? String ?? ""
            let mem = data["members"] as? [String] ?? []
            return Room(id: docId, title: title, newItems: , members: mem)
          })
        }
    }
  }

enter image description here

Upvotes: 0

Views: 590

Answers (1)

Peter Friese
Peter Friese

Reputation: 7254

You're almost there - using Codable for your data structs is the first step.

Instead of mapping your documents manually (as you do), Codable allows you to rely on the Firestore SDK and the Swift compiler to do the heavy lifting for you. I've written a comprehensive guide about how this works and how to use it, including code snippets for fetching single documents and entire collections or queries - check it out: Mapping Firestore Data in Swift - The Comprehensive Guide.

In your case, here is a modified version of the code for your repository (I wouldn't recommend putting this code Auth.swift, as the main concern is not about auth, but fetching data):

class RoomRepository: ObservableObject {
  @Published var rooms = [Room]()
  @Published var user: User

  @Published var errorMessage: String?

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

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

  func subscribe() {
    if listenerRegistration == nil {
      listenerRegistration = db.collection("rooms")
        .whereField("members", arrayContains: user.uid)
        .addSnapshotListener { [weak self] (querySnapshot, error) in
          guard let documents = querySnapshot?.documents else {
            self?.errorMessage = "No documents in 'colors' collection"
            return
          }

          self.rooms = documents.compactMap { queryDocumentSnapshot in
            let result = Result { try queryDocumentSnapshot.data(as: Room.self) }

            switch result {
            case .success(let room):
              if let room = room {
                // A Room value was successfully initialized from the DocumentSnapshot.
                self?.errorMessage = nil
                return room
              } else {
                // A nil value was successfully initialized from the DocumentSnapshot,
                // or the DocumentSnapshot was nil.
                self?.errorMessage = "Document doesn't exist."
                return nil
              }
            case .failure(let error):
              // A Room value could not be initialized from the DocumentSnapshot.
              self?.errorMessage = error.localizedDescription
              return nil
            }
          }
        }
    }
  }
}

A couple of notes:

  1. You wrapped the code for fetching data from Firestore in DispatchQueue.main.async. Instead of wrapping the entire block, you should only wrap the code that accessed the UI (i.e. the code inside the completion block).
  2. Since Firestore returns on the main thread anyway, do no not need to switch back to the main thread yourself. See this thread for more details about this.

Codable is a very powerful tool, have a look at my blog post to learn about all the different ways you can use it to map data - I cover a wide range of use cases and data types. If you find anything is missing, let me know (by filing a feature request here) and I will add it. There's also a GitHub repo with all the source code: https://github.com/peterfriese/Swift-Firestore-Guide

Upvotes: 1

Related Questions