Firebase Firestore监听器触发多次

huangapple go评论57阅读模式
英文:

Firebase Firestore Listener Fires Multiple Times

问题

我正在使用Firebase Firestore用于iOS应用程序。

我有一个承诺函数,用于循环遍历数组,并通过Firestore批量查询为每个数组项编写文档。

同时,我在该集合上有一个监听器,我的函数在其中写入文档。

为了避免监听器反复触发,我在运行承诺函数之前移除监听器。一旦承诺函数完成,我会在承诺函数的完成处理程序中重新附加监听器。

我的问题是,尽管我已经这样做了,但我注意到监听器在重新附加后会反复触发,而不只触发一次。

任何指导都将不胜感激。我尝试通过 DispatchQueue.asyncAfter 延迟重新附加监听器,观察到监听器触发的次数减少,所以问题可能与网络调用的延迟在Firestore中反映/执行有关吗?

更新:我将批处理操作拆分为单独的操作,包括写操作和删除操作。在其中一个写操作的完成处理程序中,我完成了承诺函数。这似乎提供了对我的问题的一种解决方法...但让我担心其他地方的批处理操作,是否有更好的解决方案... :/

private var listener: ListenerRegistration?

func writeTo(ids: [String]) -> Promise<Void> {

toggleListener(isActive: false)

return Promise.value(ids).thenMap { id in
    Promise<String> { resolver in

        let batch = self.db.batch()

        let refOne = self.db.collection(collectionOne).document(id)
        let refTwo = self.db.collection(collectionOne).document(id).collection(collectionTwo).document()

        batch.setData(["x": "x"], forDocument: refOne)

        batch.setData(["x": "x"], forDocument: refTwo)

        batch.commit { error in

            resolver.fulfill(id)
        }
    }
}
.done { uids in

    toggleListener(isActive: true)
}
}

func toggleListener(isActive: Bool) {

if isActive {

    listener = db.collection(collectionOne).addSnapshotListener { snapshot, error in

        print("listening")
    }

} else {

    listener?.remove()
    listener = nil
}
}
英文:

I am using Firebase Firestore for an iOS app.

I have a promise function I run that loops through an array and writes a document for each array item via a Firestore batch query.

In parallel, I have a listener on the collection which my function writes documents under.

To avoid having the listener fire repeatedly, I remove the listener prior to running the promise function. Once the promise function is complete, I reattach the listener in the promise function's completion handler.

My problem is, despite having done this, I notice that the listener fires repeatedly after attaching instead of just firing once.

Any guidance would be greatly appreciated. I tried delaying reattaching the listener via DispatchQueue.asyncAfter and have observed the number of times the listener fire go down, so perhaps the issue relates to a delay in the network call being reflected/executed in Firestore?

> Update: I split the batch operation into individual operations, which
> included both write and delete operations. In one of the write
> operations' completion handlers, I fulfill the promise function. This
> seems to offer a workaround for my issue...but does have me worried
> about other batch operations I have elsewhere and wonder if there is
> altogether better solution... :/

private var listener: ListenerRegistration?

func writeTo(ids: [String]) -&gt; Promise&lt;Void&gt; {

toggleListener(isActive: false)

return Promise.value(ids).thenMap { id in
    Promise&lt;String&gt; { resolver in

        let batch = self.db.batch()
        
        let refOne = self.db.collection(collectionOne).document(id)
        let refTwo = self.db.collection(collectionOne).document(id).collection(collectionTwo).document()
                                
        batch.setData([&quot;x&quot;: &quot;x&quot;], forDocument: refOne)
                    
        batch.setData([ &quot;x&quot;: &quot;x&quot;], forDocument: refTwo)
            
        batch.commit { error in
            
            resolver.fulfill(id)
        }
    }
}
.done { uids in
            
    toggleListener(isActive: true)
}
}

func toggleListener(isActive: Bool) {

if isActive {
    
    listener = db.collection(collectionOne).addSnapshotListener{ snapshot, error in
        
        print(&quot;listening&quot;)
    }
    
} else {
    
    listener?.remove()
    listener = nil
}
}

答案1

得分: 1

以下是翻译好的部分:

问题在于批量写操作被发送到Firestore作为一个单一请求,但Firestore会独立处理批次中的每个写入操作。这意味着如果一个写入成功而另一个失败,监听器可能会被触发多次 - 对每个成功的写入都会触发一次。

因此,更好的方法是使用事务来确保所有写入操作一起以原子方式处理,这样监听器只会被触发一次。这就是为什么使用事务而不是批量写入可以帮助您避免多次触发监听器。

这里我使用了事务而不是批量写入:

func writeTo(ids: [String]) -> Promise<Void> {
    toggleListener(isActive: false)

    return Promise.value(ids).thenMap { id in
        self.db.runTransaction { transaction -> String in
            let refOne = self.db.collection(collectionOne).document(id)
            let refTwo = refOne.collection(collectionTwo).document()
            
            transaction.setData(["x": "x"], forDocument: refOne)
            transaction.setData(["x": "x"], forDocument: refTwo)
            
            return id
        }
    }.done { uids in
        toggleListener(isActive: true)
    }
}

func toggleListener(isActive: Bool) {
    if isActive {
        if listener == nil {
            listener = db.collection(collectionOne).addSnapshotListener { snapshot, error in
                print("listening")
            }
        }
    } else {
        listener?.remove()
        listener = nil
    }
}

在这里,我们使用runTransaction而不是批量写入。事务确保对refOnerefTwo的写入是以原子方式应用的,因此监听器只会被触发一次。

参考:批量写入使用事务更新数据

英文:

The issue is that the batched writes are sent as a single request to Firestore, but Firestore processes each write in the batch independently. This means that if one write succeeds and another fails, the listener could be triggered multiple times - once for each successful write.

So a better approach would be to use a transaction that ensures that all writes are processed together, atomically, so the listener will only be triggered once. This is why using a transaction instead of a batch write can help you avoid multiple listener triggers.

Here I have use Transactions instead of batch writes:

func writeTo(ids: [String]) -&gt; Promise&lt;Void&gt; {
    toggleListener(isActive: false)

    return Promise.value(ids).thenMap { id in
        self.db.runTransaction { transaction -&gt; String in
            let refOne = self.db.collection(collectionOne).document(id)
            let refTwo = refOne.collection(collectionTwo).document()
            
            transaction.setData([&quot;x&quot;: &quot;x&quot;], forDocument: refOne)
            transaction.setData([&quot;x&quot;: &quot;x&quot;], forDocument: refTwo)
            
            return id
        }
    }.done { uids in
        toggleListener(isActive: true)
    }
}

func toggleListener(isActive: Bool) {
    if isActive {
        if listener == nil {
            listener = db.collection(collectionOne).addSnapshotListener { snapshot, error in
                print(&quot;listening&quot;)
            }
        }
    } else {
        listener?.remove()
        listener = nil
    }
}

Here, we're using runTransaction instead of a batch write. The transaction ensures that the writes to refOne and refTwo are applied atomically, so the listener will only be triggered once.

Reference : Batched writes and Updating data with transactions

huangapple
  • 本文由 发表于 2023年2月24日 09:23:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/75551843.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定