【问题标题】:How to add custom data instances to an array in Firestore?如何将自定义数据实例添加到 Firestore 中的数组?
【发布时间】:2021-09-25 00:14:48
【问题描述】:

我有一个问题,在哪里以及如何正确保存登录到房间的用户,现在我将它们保存在集合“房间”中,但我将用户保存为参考

func addUserToRoom(room: String, id: String) {
    COLLETCTION_ROOMS.document(room).updateData(["members": FieldValue.arrayUnion([COLLETCTION_USERS.document(id)])])
}

但是当我创建房间时,我需要传入一个用户数组

func fetchRooms() {
    Firestore.firestore()
        .collection("rooms")
        .addSnapshotListener { snapshot, _ in
            guard let rooms = snapshot?.documents else { return }
            
            self.rooms = rooms.map({ (queryDocumentSnapshot) -> VoiceRoom in
                let data = queryDocumentSnapshot.data()
                let query = queryDocumentSnapshot.get("members") as? [DocumentReference]
                
// ========  I'm making a request here, but I don't understand how to save users next

                query.map { result in
                    result.map { user in
                        let user = user.getDocument { userSnapshot, _ in
                            let data = userSnapshot?.data()
                            guard let user = data else { return }

                            let id = user["id"] as? String ?? ""
                            let first_name = user["first_name"] as? String ?? ""
                            let last_name = user["last_name"] as? String ?? ""

                            let profile = Profile(id: id,
                                                  first_name: first_name,
                                                  last_name: last_name)
                        }
                    }
                }

                let id = data["id"] as? String ?? ""
                let title = data["title"] as? String ?? ""
                let description = data["description"] as? String ?? ""
                let members = [Profile(id: "1", first_name: "Test1", last_name: "Test1")]
                
                return VoiceRoom(id: id,
                                 title: title,
                                 description: description,
                                 members: members
                })
        }
}

这就是我的房间和个人资料模型的样子

struct VoiceRoom: Identifiable, Decodable {
    var id: String
    var title: String
    var description: String
    var members: [Profile]?
}

struct Profile: Identifiable, Decodable {
    var id: String 
    var first_name: String?
    var last_name: String?
}

也许有人可以告诉我,如果我没有正确保存用户,我需要在单独的集合中进行,到目前为止我还没有找到解决方案,我将不胜感激。

【问题讨论】:

  • "登录房间的用户" 你是唯一了解这些实体之间关系的人。而且你没有解释它。
  • 您可以将用户的 UID 存储在该房间文档中的数组中。
  • @ElTomato 我在我的数据库中添加了一张图片
  • 不幸的是,图像不是很有帮助,而且术语“创建房间”和“保存用户”在没有更多上下文的情况下有点含糊。我想我的问题是;如果您有一个房间文档,并且其中一个字段是存储 userId 的数组,那么将代码中的数据加载到保存数据的类或其他对象有什么问题?在这种情况下,您只是将 uid 加载到带有数组的对象中。不?你能澄清一下这个问题吗?

标签: swift firebase firebase-realtime-database google-cloud-firestore swiftui


【解决方案1】:

我觉得这个答案会让你大吃一惊:

struct VoiceRoom: Identifiable, Codable {
    var id: String
    var title: String
    var description: String
    var members: [Profile]?
}

struct Profile: Identifiable, Codable {
    var id: String
    var first_name: String?
    var last_name: String?
}

final class RoomRepository: ObservableObject {
    @Published var rooms: [VoiceRoom] = []
    private let db = Firestore.firestore()
    private var listener: ListenerRegistration?
    
    func addUserToRoom(room: VoiceRoom, user: Profile) {
        let docRef = COLLECTION_ROOMS.document(room.id)
        let userData = try! Firestore.Encoder().encode(user)
        docRef.updateData(["members" : FieldValue.arrayUnion([userData])])
    }
    func fetchRooms() {
        listener = db.collection("rooms").addSnapshotListener { snapshot, _ in
            guard let roomDocuments = snapshot?.documents else { return }
            self.rooms = roomDocuments.compactMap { try? $0.data(as: VoiceRoom.self) }
        }
    }
}

就是这样。这就是正确存储房间成员并简单地将存储的房间解码为VoiceRoom 的实例所需的全部内容。所有这些都是不言自明的,但如果您有任何问题,请随时在 cmets 中提问。

附:我把COLLETCTION_ROOMS改成了COLLECTION_ROOMS

编辑:

无论如何,我决定详细说明我的更改,以便刚开始编码的人可以理解我如何将代码减少到几行(如果您理解我的代码,请跳过此部分)。

当您的自定义数据实例符合Codable 协议时,它允许这些实例自动转换为数据库可以存储的数据(称为编码),并允许它们在您检索时转换回代码中的具体类型它们来自数据库(称为解码)。它的神奇之处在于您只需将: Codable 添加到您的结构和类中即可免费获得所有这些功能!

现在,函数呢? addUserToRoom(room:user:) 函数接受VoiceRoom 而不是String,因为调用者通常更容易传入room 而不是room.id。这个额外的步骤可以由函数本身完成,并在视图中节省一点混乱,同时也使其更容易。

最后,fetchRooms() 函数将侦听器附加到“房间”集合,并在集合发生任何变化时触发代码块。在代码块中,我使用guard let roomDocuments = snapshot?.documents else { return } 检查文档快照是否实际包含文档。在我知道我拥有文档之后,剩下要做的就是将这些文档转换回 VoiceRoom 实例,这很容易完成,因为 VoiceRoom 符合 Codable (记住我说过的话:“并且允许它们转换回从数据库中检索代码中的具体类型”)。我通过将[QueryDocumentSnapshot] 即roomDocuments 的数组映射到具体的VoiceRoom 实例来做到这一点。我使用紧凑型地图,因为如果任何文档无法解码,它将不会包含在 self.rooms 中。

所以
try? $0.data(as: VoiceRoom.self)
尝试获取每个文档(由$0表示)并表示其“数据” “as” “VoiceRoom” 个实例。

仅此而已!

【讨论】:

  • 非常感谢您提供如此详细的答案,我知道在您的示例中一切都是自动完成的,但我也想知道这种方式 let members = data["members"] as ? [轮廓] ?? [] 那么能做到吗?
  • 可以。只需将self.rooms = ... 移动到最里面的完成处理程序。获得所需的所有数据后,您可以将最终的 VoiceRoom 数组分配给 self.rooms。但是我强烈建议不要这样做,因为我的方法可以节省您的阅读量,这反过来又可以节省您的钱(如果您打算使这个应用程序成为商业产品,而不仅仅是作为一种爱好)。它还可以提供更好的用户体验,因为它速度更快,而且只需 1 个网络调用,而不是数千个小调用。
猜你喜欢
  • 2020-12-28
  • 2018-03-16
  • 1970-01-01
  • 2021-12-24
  • 2021-02-07
  • 1970-01-01
  • 2021-01-02
  • 1970-01-01
  • 2023-03-09
相关资源
最近更新 更多