Firebase on Swift, how to get notified if a query is empty? - swift

Hello I am using firebase in swift and I am trying to search through my users by user name using a query like so:
InfoCenter.ref.child("users").queryOrderedByChild("username").queryEqualToValue(firstTextField.text)
.observeEventType(.ChildAdded, withBlock: { snapshot in
This works great but I want to be notified if the query is empty aka the there is no user that has that user name. Can someone please show me how to edit how I format my search to allow for that? Thanks!
Edit:
My tree looks like this:
users:{
asdfasdfkl:{
username: "bob",
highscore: "1000"
}
}
upon changing event type to .Value, when using snapshot.value I no longer get the adjkadfjla ID. I now get the entire try below it

you can check if the snapshot exist, this will return a Bool
snapshot.exists()

for Swift 5.0, it's been updated to if (querySnapshot?.isEmpty == true) {}

Related

Fetch a random record from a Realm database (Swift)

I am trying to fetch a random record from my Swift Realm database.
I have previously used the sample function in mongoDB, so I thought there must be an equivalent in Realm (it's based on mongoDB, right?)
I can't find any documentation on such a function, and I've only found search results which suggest to fetch the entire collection then choose a random record [1, 2]. Obviously, this is inefficient.
Am I missing something obvious, is there a better way to do it?
See below for an example mongoDB query:
db.Words.aggregate([
{ $match: { gender: gender } },
{ $sample: { size: 1 } }
])
For clarity the code in the question is not part of the Realm Swift SDK for local or sync but it is a query directly to Atlas using app services, so it would be valid for non-sync'ing or non-local applications. (Use the Swift SDK if possible!)
If we're doing this using the SDK, you can actually leverage high-level Swift functions to return a result using .randomElement()
So given a PersonClass that has a name property
class PersonClass: Object {
#Persisted var name = ""
}
we can this use this code to return a random person from Realm and output their name to console
if let randomPerson = realm.objects(PersonClass.self).randomElement() {
print(randomPerson.name)
} else {
print("no data was returned)")
}

Check if a new field was added in a specific document like documentChanges for a collection in Firestore

I use this code for load comments in a table view:
func observePostComments(postId: String, completion: #escaping (String) -> Void) {
let db = Firestore.firestore()
db.collection("post-comments").document(postId).addSnapshotListener { (snapshot, err) in
if snapshot!.exists {
for key in (snapshot?.data()!.keys)! {
completion(key)
}
} else {
return
}
}
}
It works like it should, but every time a user creates a new comment, all comments are added again. I know how it works for a collection with:
querySnapshot?.documentChanges.forEach { diff in
if (diff.type == .added) { ....
But I can not figure out how to implement that functionality on a document / field level. If I want to do the same on a document level, I receive
Value of type 'DocumentSnapshot?' has no member 'documentChanges'.
How can I track changes on a specific document level, when a new Key-Value pair was added to a document?
Firestore's change detection only works on complete documents. If you need to know what changed inside a document, you will have to detect this in your own code, for example by comparing the previous DocumentSnapshot with the new one.
The exact way to do this depends a bit on what data you store, but there are two broad approaches:
You take something that is unique about each comment, and check if that's already present in your UI. This can for example be the ID of each comment, but anything else that's unique works too.
You store a timestamp for each comment, and keep track of the most recent timestamp you've already processed. Then in an update, you skip all comments up until that timestamp.
Another approach would be to clear the UI before adding the same comments to it. So something like:
db.collection("post-comments").document(postId).addSnapshotListener { (snapshot, err) in
if snapshot!.exists {
clearCommentsFromUI() // this is a function you will have to implement
for key in (snapshot?.data()!.keys)! {
completion(key)
}

FireStore - how to get around array "does-not-contain" queries

After some research, it's seems clear that I cannot use FireStore to query items a given array does NOT contain. Does anyone have a workaround for this use case?...
After a user signs up, the app fetches a bunch of cards that each have a corresponding "card" document in FireStore. After a user interacts with a card, the card document adds the user's uid to a field array (ex: usersWhoHaveSeenThisCard: [userUID]) and the "user" document adds the card's uid to a field array (ex: cardsThisUserHasSeen: [cardUID]). The "user" documents live in a "user" collection and the "card" documents live in a "card" collection.
Currently, I'd like to fetch all cards that a user has NOT interacted with. However, this is problematic, as I only know the cards that a user has interacted with, so a .whereField(usersWhoHaveSeenThisCard, arrayContains: currentUserUID) will not work, as I'd need an "arrayDoesNotContain" statement, which does not exist.
Finally, a user cannot own a card, so I cannot create a true / false boolian field in the card document (ex: userHasSeenThisCard: false) and search on that criteria.
The only solution I can think of, would be to create a new field array on the card document that includes every user who has NOT seen a card (ex: usersWhoHaveNotSeenThisCard: [userUID]), but that means that every user who signs up would have to write their uid to 1000+ card documents, which would eat up my data.
I might just be out of luck, but am hoping someone more knowledgeable with NOSQL / FireStore could provide some insight.
// If any code sample would help, please let me know and I'll update - I think this is largely conceptual as of now
As you've discovered from query limitations, there is no easy workaround for this using Cloud Firestore alone. You will need to somehow store a list of documents seen, load that into memory in the client app, then manually subtract those documents from the query results of all potential documents.
You might want to consider augmenting your app with another database that can do this sort of operation more cleanly (such as a SQL database that can perform joins and subqueries), and maintain them in parallel.
Either that, or require all the documents to be seen in a predictable order, such as by timestamp. Then all you have to store is the timestamp of the last document seen, and use that to filter the results.
There is an accepted and good answer, however, it doesn't provide a direct solution to the question so here goes... (this may or may not be helpful but it does work)
I don't know exactly what your Firestore structure is so here's my assumption:
cards
card_id_0
usersWhoHaveSeenThisCard
0: uid_0
1: uid_1
2: uid_2
card_id_1
usersWhoHaveSeenThisCard
0: uid_2
1: uid_3
card_id_2
usersWhoHaveSeenThisCard
0: uid_1
1: uid_3
Suppose we want to know which cards uid_2 has not seen - which in this case is card_id_2
func findCardsUserHasNotSeen(uidToCheck: String, completion: #escaping ( ([String]) -> Void ) ) {
let ref = self.db.collection("cards")
ref.getDocuments(completion: { snapshot, err in
if let err = err {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else {
print("no docs")
return
}
var documentsIdsThatDoNotContainThisUser = [String]()
for doc in docs {
let uidArray = doc.get("usersWhoHaveSeenThisCard") as! [String]
let x = uidArray.contains(uidToCheck)
if x == false {
documentsIdsThatDoNotContainThisUser.append(doc.documentID)
}
}
completion(documentsIdsThatDoNotContainThisUser)
})
}
Then, the use case like this
func checkUserAction() {
let uid = "uid_2" //the user id to check
self.findCardsUserHasNotSeen(uidToCheck: uid, completion: { result in
if result.count == 0 {
print("user: \(uid) has seen all cards")
return
}
for docId in result {
print("user: \(uid) has not seen: \(docId)")
}
})
}
and the output
user: uid_2 has not seen: card_id_2
This code goes through the documents, gets the array of uid's stored within each documents usersWhoHaveSeenThisCard node and determines if the uid is in the array. If not, it adds that documentID to the documentsIdsThatDoNotContainThisUser array. Once all docs have been checked, the array of documentID's that do not contain the user id is returned.
Knowing how fast Firestore is, I ran the code against a large dataset and the results were returned very quickly so it should not cause any kind of lag for most use cases.

How to store data in Firestore (Swift)

I have an iOS app using Cloud Firestore and have problems with updating the data. My goal is to add urls to a dictionary one by one, but all I get is rewritten one value. How should I use setData and updateData? Tried it different ways
storageRef.child("users/" + currentUser.value!.documentID + "/" + faceRef.documentID + ".jpg")
.putData(data!).observe(.success) { (snapshot) in
guard let downloadURL = snapshot.metadata?.downloadURL()?.absoluteString else { return }
let db = self.fsReference.document(self.currentUser.value!.documentID)
var dict = ["faces": ["": ""]]
dict["faces"] = ["newvalue\(downloadURL.hashValue)": downloadURL]
db.updateData(dict)
completion?()
Here's what I tried. Any advice would be nice, thanks in advance!
UPD: Tried to move my dictionary to subcollection, but after .collection("newCollection").document("newdocument") collection does not appear. What might be the problem?
So what I am seeing is you are using cloud storage to save profile pictures and you want to save each one of the urls those pictures. You need to understand that both setValue() and updateValue() do just about the same thing. A note with updateValue() is it will create that document if it doesn't already exist. So, when updating values in Firestore understand that it sets the value to what you give it, which can be misleading at first.
1st When updating any document start by getting the document first. If people are constantly updating different document you may want to consider using Firestore transactions: https://firebase.google.com/docs/firestore/manage-data/transactions#transactions
This will make sure that your data is updated correctly.
2nd Append the URL to the to the array, I am not how you set it up, but I would setup the firestore to look something like this
"users" = [
"unique_id = "{
"firstname": "John",
"lastname": "Doe",
"unique_id": "document_id_here"
"faces": [ {key: value} ]
}
]
When you serialize that object your faces object should be this [[String: Any]]
3rd, last step would be to get the document and update just that value
// Get the value in the completion with the data use this code
// Drill down to the property you want to update using the completion data ex.
var faces = completedData.faces
faces.append("[key: value]")
// Update the data back to firestore
let path = Firestore.firestore().collection("users").document("unique_user_id")
// Merging is so important. otherwise it will override your document
path.setData(["facesKey: faces"], merge: true) {(error in
if let error = error {
// good error handling here
}
// Successfully updated document
)}

How do I retrieve a key from a firestore document (using swift 4/ios)

In the firebase docs for firebase database there is info on flattening a database and a message app is used as an example.
https://firebase.google.com/docs/database/web/structure-data
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
Under "members"/"one" each key is the name of a participating member in the chat. I'm pretty sure in firebase database there is a getKey method to get each key.
I have set up my database (using firestore instead of firebase) in a similar way but where each chat is a unique identifier and the document contains the members of the chat where the key is their firebase auth id eg.
var docsref: DocumentReference?
docsref = self.defaultStore?.colleection("chats").document(chatID)
docsref?.getDocument { (document, error) in
if let _ = document {
print("document!.data()")
} else {
print("Document does not exist")
}
}
And when I output:
print(document!.data())
I get ["2mHuccccxxxxxxxxxxk4wkIAt33": 1] which is the firebase auth code of one of the participating chat members and a boolean.
I would like to get that key but wasn't sure how.
Thanks.
I'm not sure if it's the exact Syntax but self.defaultStore?.colleection might be a typo with the double E. It could also possibly be that you're missing a step. You're getting inside the chatID and you're expecting to output the keys inside members without actually accessing it. I'd access members after .document(chatID)
You can get it as follows.
to get value document.data()
to get key document.documentID