Firebase completion listeners in swift
The completion of a setValue is handled within the {} block (closure). So once the attempt to setValue is made, the code within that block executes. error will be nil if none and snapshot will be the data that was written.
let ref = self.myRootRef.child("some_path")
ref.setValue("Hello", withCompletionBlock: { (error, snapshot) in
if error != nil {
print("oops, an error")
} else {
print("completed")
}
})
gives a result of
root_ref
some_path: Hello
and prints "completed"
How to know when all initial data is fetched from a root node using listeners in Realtime Firebase database
There are two ways to do this, but they both depend on the same guarantee that Firebase makes about the order in which events are fired.
When you observe both child
events and value
events on the same path/query, the value
event fires after all corresponding child
events.
Because if this guarantee, you can add an additional listener to .value
rootNodeReference.observeSingleEvent(of: DataEventType.value, with: { snapshot in
... the initial data is all loaded
})
Adding the second listener doesn't increase the amount of data that is read from the database, because Firebase deduplicates them behind the scenese.
You can also forego the childAdded
listener and just use a single observe(.value
as shown in the documentation on reading a list by observing value events:
rootNodeReference.observe(.value) { snapshot in
for child in snapshot.children {
...
}
}
How to solve the problem with Firebase listener, which open everywhere?
Since you're listening for for auth state changes, you don't need to handle the self?.showNextVC()
in the completion callback for signIn(withEmail:, password:)
. That code should only be present in the callback for addStateDidChangeListener
.
Alternatively, you can:
- Use the
addStateDidChangeListener
to initially detect whether the user is signed-in already. - Inside the callback for the state change:
- Remove the listener by calling
removeAuthStateDidChangeListener
- Start the explicit sign-in flow, and call
signIn(withEmail:, password:)
like you're doing now.
- Remove the listener by calling
How can I remove listener upon completion of task in closure?
If your listener just unregisters itself immediately in the callback, you probably don't want a listener at all. Instead, use getDocuments() to fetch the results of the query a single time, and you won't have to worry about unregistering any listeners.
How to use Swift's new Async/Await features with Firestore Listeners
OK, like I said in my comment I don't think this is the right use case for Async/Await. Async/Await is more suited to asynchronous functions where you would receive back a single response. For instance a REST api that returns some value.
Incidentally, the Firestore function .getDocument()
now has async/await alternatives.
However, the addSnapshotListener
is something that will return multiple values over time and call the callback function over and over again.
What we can do with this, though, is to turn it into a Combine Publisher.
Here I've created a small FirestoreSubscription
struct that you can use to subscribe to a document path...
import Combine
import FirebaseFirestore
import FirebaseFirestoreSwift
struct FirestoreSubscription {
static func subscribe(id: AnyHashable, docPath: String) -> AnyPublisher<DocumentSnapshot, Never> {
let subject = PassthroughSubject<DocumentSnapshot, Never>()
let docRef = Firestore.firestore().document(docPath)
let listener = docRef.addSnapshotListener { snapshot, _ in
if let snapshot = snapshot {
subject.send(snapshot)
}
}
listeners[id] = Listener(document: docRef, listener: listener, subject: subject)
return subject.eraseToAnyPublisher()
}
static func cancel(id: AnyHashable) {
listeners[id]?.listener.remove()
listeners[id]?.subject.send(completion: .finished)
listeners[id] = nil
}
}
private var listeners: [AnyHashable: Listener] = [:]
private struct Listener {
let document: DocumentReference
let listener: ListenerRegistration
let subject: PassthroughSubject<DocumentSnapshot, Never>
}
The subscribe
function returns an AnyPublisher<DocumentSnapshot, Never>
(so currently it doesn't handle any errors.
I also created a FirestoreDecoder
that will decode DocumentSnapshot
into my own Codable
types...
import Firebase
struct FirestoreDecoder {
static func decode<T>(_ type: T.Type) -> (DocumentSnapshot) -> T? where T: Decodable {
{ snapshot in
try? snapshot.data(as: type)
}
}
}
I created a really simple Firestore document...
And a struct that we will decode from that document...
struct LabelDoc: Codable {
let value: String?
}
Now in my ViewController
I can subscribe to that document path and decode and set it onto a label...
import UIKit
import Combine
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
var cancellables: Set<AnyCancellable> = []
struct SubscriptionID: Hashable {}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
FirestoreSubscription.subscribe(id: SubscriptionID(), docPath: "labels/title")
.compactMap(FirestoreDecoder.decode(LabelDoc.self))
.receive(on: DispatchQueue.main)
.map(\LabelDoc.value)
.assign(to: \.text, on: label)
.store(in: &cancellables)
}
}
This is just a quick example project so there may be better ways of doing this but now I can update the value in Firestore and it will immediately update on the screen /p>
You could probably even wrap up that subscription into a function that could be used in multiple places.
Related Topics
Modal View Closes When Selecting an Image in Uiwebview iOS
Possible to Pass an Enum Type Name as an Argument in Swift
How to Implement Protocol Methods That Return Covariant Selfs
Nsurlerrordomain with Code=-1100
Injecting a New Stylesheet into a Website via Uiwebview Using iOS8 Swift Xcode 6
How to Transfer the User's Score to Another Scene in Swift and Spritekit
How to Hide Status Bar and Navigation Bar When Tap Device
Environmentobject VS Singleton in Swiftui
How to Tell Which Guard Statement Failed
Swift Protocol to Require Properties as Protocol
Add Constraints to Generic Parameters in Extension
Using Uiapplicationdelegateadaptor to Get Callbacks from Userdidacceptcloudkitsharewith Not Working
Load Desktop Version Wkwebview iOS 9
Turn for in Loops Local Variables into Mutable Variables
In Swift,There's No Way to Get the Returned Function's Argument Names