如何使用onSnapshot()方法迅速正确利用Firestore实时更新?

时间:2019-08-08 21:14:04

标签: swift firebase google-cloud-firestore

我是Google Firestore的新手,并且通常都很敏捷,并且想知道如何正确设置onSnapshot()方法,以便我的视图控制器可以自动接收更新并适合所有情况。

我有一个非常简单的结构(Llama),可以用来对数据进行建模。通过遵循以下出色的教程,我可以构建一个基本的Firestore设置:(https://youtu.be/XwXEsKRYUXU

但是,我发现在实现自己的代码版本时,我只知道如何在服务器上创建文档。如果服务器上发生更改,我希望能够更新我的应用程序中的对象,并且我也想删除一个对象。目前,我对如何完成此任务一无所知。

此外,在更新我的应用程序中的对象时,还有一种方法可以只更新已更改的字段,而不是覆盖整个对象。

我想完成的是一个数据库模型,该模型在后台在托管服务器和用户设备之间无缝同步,如果有冲突,则将任何人的更改合并到一起(由上次修改文档的人解决)。 )

我已经详尽阅读了有关该主题的Google文档,即使我确定可以找到答案,但我个人还没有很快地了解到它们的全部知识:(https://cloud.google.com/firestore/docs/how-to

数据模型:

import Foundation
import Firebase

protocol DocumentSerializable {
    init?(dictionary:[String:Any])
}

struct Llama {

    var name: String
    var color: String
    var gender: String

    init(name: String, color: String, gender: String) {
        self.name = name
        self.color = color
        self.gender = gender
    }

    var dictionary:[String:Any] {
        return [
            "name":name,
            "color":color,
            "gender":gender
        ]
    }

}

extension Llama:DocumentSerializable {
    init? (dictionary: [String:Any]) {
        guard
            let name = dictionary["name"] as? String,
            let color = dictionary["color"] as? String,
            let gender = dictionary["gender"] as? String
            else {return nil}
        self.init(name: name, color: color, gender: gender)
    }
}

查看控制器代码:

    var llamas = [Llama]()

    override func viewDidLoad() {
        super.viewDidLoad()
        checkForUpdates()
    }

    func checkForUpdates() {
        let firestore = Firestore.firestore()
        firestore.collection("Llama").addSnapshotListener{
            QuerySnapshot, error in
            guard let snapshot = QuerySnapshot else {return}
            snapshot.documentChanges.forEach {
                update in
                if update.type == .added {
                    self.llamas.append(Llama(dictionary: update.document.data())!) // Works great!
                }
                if update.type == .modified {
                    // How can I update the correct llama object, hopefully just the field(s) that changed?
                }
                if update.type == .removed {
                    // How can I remove the correct llama object?
                }
            }
        }
    }

如View Controller代码中所示,我有一个函数,该函数在View Controller打开时被调用,该函数初始化addSnapShotListener()方法。原样的代码不会运行任何错误或警告。我该如何前进?

1 个答案:

答案 0 :(得分:1)

这个问题有几个部分,要解决所有这些问题将是一个很长的答案。这是一个解决方案,将首先加载您的dataSource数组,然后监视和处理谨慎的``对象''的添加,修改和删除事件,例如文档。

在此示例中,我们将使用一个可跨设备跟踪喜爱的葡萄酒的应用。我们会跟踪葡萄酒的名称,产地和等级。

结构是这个

wines
   doc_0
      name: "Insignia"
      rating: "98"
      state: "CA"
   doc_1
      name: "Quilceda Creek"
      rating: "99"
      state: "WA"

doc_0,doc_1等是添加文档时自动创建的文档ID。

然后我们需要一个班级来举办每种葡萄酒

class WineClass {
    var wine_id = ""
    var name = ""
    var rating = ""
    var state = ""

    init(withDoc: QueryDocumentSnapshot) {
        self.wine_id = withDoc.documentID
        self.name = withDoc.get("name") as? String ?? "no name"
        self.rating = withDoc.get("rating") as? String ?? "no rating"
        self.state = withDoc.get("state") as? String ?? "no state"
    }

    func updateProperties(withDoc: QueryDocumentSnapshot) {
        self.name = withDoc.get("name") as? String ?? "no name"
        self.rating = withDoc.get("rating") as? String ?? "no rating"
        self.state = withDoc.get("state") as? String ?? "no state"
    }
}

最后是一个类var Array,它将用作tableView或collectionView的数据源。

var wineArray = [WineClass]()

然后是代码-我们仅对CA的葡萄酒感兴趣,因此查询将结果限制为仅此示例中的结果。

func handleSpecificChanges() {
    let collectionRef = self.db.collection("wines")
    collectionRef.whereField("state", isEqualTo: "CA").addSnapshotListener { querySnapshot, error in
        guard let snapshot = querySnapshot else {
            print("Error fetching snapshots: \(error!)")
            return
        }

        snapshot.documentChanges.forEach { diff in
            if (diff.type == .added) {
                let wineToAdd = WineClass(withDoc: diff.document)
                self.wineArray.append(wineToAdd)
                print("added: \(wineToAdd.wine_id)  \(wineToAdd.name) \(wineToAdd.rating)  \(wineToAdd.state)")
            }
            if (diff.type == .modified) {
                let docId = diff.document.documentID
                if let indexOfWineToModify = self.wineArray.firstIndex(where: { $0.wine_id == docId} ) {
                    let wineToModify = self.wineArray[indexOfWineToModify]
                    wineToModify.updateProperties(withDoc: diff.document)
                    print("modified: \(wineToModify.wine_id)  \(wineToModify.name) \(wineToModify.rating)  \(wineToModify.state)")
                }
            }
            if (diff.type == .removed) {
                let docId = diff.document.documentID
                if let indexOfWineToRemove = self.wineArray.firstIndex(where: { $0.wine_id == docId} ) {
                    self.wineArray.remove(at: indexOfWineToRemove)
                    print("removed: \(docId)")
                }
            }
        }
    }
}

一些笔记

docId(doc_0,doc_1)等是将其全部结合在一起的密钥。保证它是唯一的,这是您知道哪种酒已被修改或删除的方式,以及在Firestore中创建该酒时对其进行分配的方式。

此代码最初运行时,将为每个与查询匹配的葡萄酒调用.add。这样一来,您就可以在初始时使用匹配项填充dataSource数组,然后再监视添加的事件。

您的问题之一是在不完全覆盖对象的情况下更新对象的字段。这可能并不重要-从修改后的事件中可以看出,我们只是将当前字段传递给对象以更新所有字段。如果您确实不希望“覆盖”字段,则可以添加if检查以查看传入的字段数据是否与实际不同。

if self.rating != newRatnig {
   self.rating == newRating
}

但同样,这可能不是必需的。

我认为这解决了所有问题。