How to query multiple fields with one value in Firebase? - swift

I'm a newbie at firebase I have implemented a sample app that able to transfer point to each other after transfer success I also added two fields called "sender_name" and "receiver_name" but it's too difficult to get all transitions based on user login I found sample ways to do just add multiple where to it, its work fine if true both but that's not what I want I want whereOr like SQL as an example below
SELECT column1, column2, ...
FROM table_name
WHERE condition1 OR condition2 OR condition3 ...;
any solution help, please
func getUserTransition(){
// process
/*
1.get all transition from tm_members sender and receiver by current user login
2.
*/
guard let username = self.userSession?.username else {
return
}
print("username in user session : \(username)")
COLLECTION_TM_TRANSITIONS_UAT
.whereField("sender_name", isEqualTo: username)
.whereField("receiver_name", isEqualTo: username)
.getDocuments { documentSnapshot, error in
if error == nil {
guard let value = documentSnapshot?.documents else { return }
self.tmTransitions = value.map { (queryDocumentSnapshot) -> TmTransition in
let data = queryDocumentSnapshot.data()
let email = data["email"] as? String ?? ""
let is_sender = data["is_sender"] as? Bool ?? false
let point = data["point"] as? Int ?? 0
let username = data["username"] as? String ?? ""
let sender_id = data["sender_id"] as? String ?? ""
let receiver_id = data["receiver_id"] as? String ?? ""
let created_at = data["created_at"] as? Timestamp
let sender_name = data["sender_name"] as? String ?? ""
let receiver_name = data["receiver_name"] as? String ?? ""
print("username : \(email)")
return TmTransition(id: queryDocumentSnapshot.documentID, sender_id: sender_id, receiver_id: receiver_id, username: username, is_sender: is_sender, point: point, email: email,created_at: created_at,sender_name: sender_name,receiver_name: receiver_name)
}
}
else{
print("error during fetch data ")
}
}
}

Related

Firebase query returns empty when data is there

I have this RTDB I am trying to search my users in, from a path called users -> UID -> and then the user key/values. One of them being "username". I want to append these to an array to return in my table view but no matter what I do, I keep getting back nothing.
var userRef = Database.database().reference(withPath: "users")
func queryText(_ text: String, inField child: String) {
print(text)
userRef.queryOrdered(byChild: child)
.queryStarting(atValue: text)
.queryEnding(atValue: text+"\u{f8ff}")
.observeSingleEvent(of: .value) { [weak self] (snapshot) in
for case let item as DataSnapshot in snapshot.children {
//Don't show the current user in search results
if self?.currentUser?.uid == item.key {
continue
}
if var itemData = item.value as? [String:String] {
itemData["uid"] = item.key
self?.resultsArray.append(itemData)
print(self?.resultsArray)
}
}
self?.tableView.reloadData()
}
}
Edit: I have verified I am able to print out the snapshot, I am just not getting the usernames added to my resultsArray. Anyone have a clue why?
bio = " dfdf";
displayname = chattest4;
email = "test#test.com";
"first_name" = chattest4;
followers = 0;
following = 0;
"join_date" = "June 28, 2021";
languages = English;
"last_name" = test;
location = "United States";
profileImageURL = "hidjkfsf";
username = chattest4;
So I found out the issue. Some of the values in my database were not strings, but I had coded it to only look for Strings. Username was stored after these non-string values, so it never reached it. I just changed the array to [String:Any] and then it worked!
if var itemData = item.value as? [String:Any]

function with closure not working correctly

on my social app in swiftUI I'm using firebase as backend, unfortunately
I find out that sometimes the listener (I don't know why) download 2/3 time the same change, so with some stackOverflow help I came up with the following to function in order to listen the change and publish it on the view.
func getuserConfirmFriend(userLoggato: UserModel, onSuccess: #escaping([UserModel]) -> Void, onError: #escaping(_ errorMessage: String) -> Void, newPendingUser: #escaping(UserModel) -> Void, userRemoved: #escaping(UserModel) -> Void , listener: #escaping(_ listenerHandle: ListenerRegistration) -> Void){
let listenerRegistration = db.collection("user").document(userLoggato.userID).collection("confimFriend").addSnapshotListener(includeMetadataChanges: false) { documentSnapshot, error in
var userConfirmFriendsArray = [UserModel]()
guard let snapshot = documentSnapshot else { return }
snapshot.documentChanges.forEach { (documentChange) in
switch documentChange.type {
case .added :
let dict = documentChange.document.data()
let name = dict["name"] as? String ?? "na name"
let surname = dict["surname"] as? String ?? "na name"
let email = dict["email"] as? String ?? "na name"
let userLevel = dict["adminLevel"] as? String ?? "unable to get admin level"
let idUser = dict["userID"] as? String ?? "no ID"
let position1 = dict["position"] as? String ?? "na preferance position"
let position2 = dict["position2"] as? String ?? "na preferance position"
let vote = dict["vote"] as? Int ?? 0
self.downloadImageForAdmin(userID: idUser) { (urlImage) in
let utente = UserModel(name: name, surname: surname, email: email, userID: idUser, adminLevel: userLevel, immagine: urlImage, position: position1, position2: position2, vote: vote)
newPendingUser(utente)
userConfirmFriendsArray.append(utente)
// if I put onSuccess here it work but keep duplicating the result
}
print("CONFIRM User Added")
case .modified :
//implements action (new escaping)
print("CONFIRM User Modified ")
case .removed :
print("CONFIRM User Removed")
let dict = documentChange.document.data()
let name = dict["name"] as? String ?? "na name"
let surname = dict["surname"] as? String ?? "na name"
let email = dict["email"] as? String ?? "na name"
let userLevel = dict["adminLevel"] as? String ?? "unable to get admin level"
let idUser = dict["userID"] as? String ?? "no ID"
let position1 = dict["position"] as? String ?? "na preferance position"
let position2 = dict["position2"] as? String ?? "na preferance position"
let vote = dict["vote"] as? Int ?? 0
self.downloadImageForAdmin(userID: idUser) { (urlImage) in
let utente = UserModel(name: name, surname: surname, email: email, userID: idUser, adminLevel: userLevel, immagine: urlImage, position: position1, position2: position2, vote: vote)
userRemoved(utente)
}
}
}
onSuccess(userConfirmFriendsArray)
// Problem Here, not working this closure onSuccess
// Any reason why this closure always return an empty array?? should be filled on .added case
}
listener(listenerRegistration)
}
the problem I'm finding is: by my small knowledge, the closure onSuccess should be put after the forEach.
Like this I can use the userConfirmFriendsArray:[UserModel] (see my comment on the code)
but looks like it always return an empty array, why??? if the case .added append the user inside
userConfirmFriendsArray.
here below how I use getuserConfirmFriend()
func loadUserConfirmFriends(userLoggato: UserModel){
self.userConfirmFriends = []
// self.isLoading = true
getuserConfirmFriend(userLoggato: userLoggato, onSuccess: { (users) in
if self.userConfirmFriends.count == 0 {
self.userConfirmFriends = users
}
// self.isLoading = false
},
onError: { (errorMessage) in
print("Error Message \(errorMessage)")
},
newPendingUser: { (user) in
if self.userConfirmFriends.count > 0 {
self.userConfirmFriends.append(user)
}
}, userRemoved: {(userRemoved) in
let index = self.userConfirmFriends.firstIndex { (user) -> Bool in
userRemoved.userID == user.userID
}
if index == nil {
return
} else {
if self.userConfirmFriends.count >= 1 {
self.userConfirmFriends = []
} else {
self.userConfirmFriends.remove(at: index!)}}
})
{ (listener) in
self.listener = listener
}
}
in order to see the change I have to put the onSuccess(userConfirmFriendsArray) closure inside the case .added:, but why?? can't be put after the forEach? if I put there always gave me an empty array.
thanks
because snapshot.documentChanges.forEach doing things and call their closure async in different thread
so when you call onSuccess(userConfirmFriendsArray) line after snapshot.documentChanges.forEach the closure not called yet
and you have self.downloadImageForAdmin(userID: idUser) that call the closure async in different thread too
you can read how multithreading work

How to find out if an attribute is in the table for a user?

I have a firebase table where I need to find out if an attribute exists in the table for a particular user. Table is structured like this:
Users
-Lf9xUh53VeL4OLlwqQo
username: "my#test.com"
price: "$100"
special: "No"
-L12345ff322223345fd
username: "my2#test.com"
special: "No"
I need to find out if the "price" has been added for a specific user. Can't seem to figure that one out!
In swift I need something like:
self.ref?.child("Users").queryOrdered(byChild: "username").queryEqual(toValue: username.text!).observe(.value, with: { (snapShot) in
if (snapShot.value! is NSNull) {
print("nothing found")
} else {
print("found it!")
print(snapShot)
let snapShotValue = snapShot.value as! [String:[String:Any]]
Array(snapShotValue.values).forEach { // error here if it doesn't exist
let price = $0["price"] as! String
self.userPrice.text = price
}}
})
But if the price doesn't exist I'm getting an error. Thanks for your help.
Use as? instead of as!
if let price = $0["price"] as? String {
print(price)
}
else {
print("No price")
}
Or shortly
self.userPrice.text = ($0["price"] as? String) ?? "No price"

Getting old value of field in Firestore with Swift 5

I'm trying to get the old value of a field when it is changed in Firestore. Is there any way to access what the previous value of a field is after it is changed? Here is my current code, I want to access the old nickName under .modified and print out what the new nickName is and also the old one.
db.collection("cities").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) {
print("New city: \(diff.document.data())")
let nickName = myData["nickName"] as? String ?? ""
}
if (diff.type == .modified) {
let nickName = myData["nickName"] as? String ?? ""
}
if (diff.type == .removed) {
let nickName = myData["nickName"] as? String ?? ""
}
}
}
Unfortunately, that is not a feature of Firestore. What you can do is have another field oldNickName and using Firebase Functions, automatically update that when the nickName field is changed.
The best solution is storing nickName locally, so you can refer back to your local variable when nickName changes, accessing the newly updated one in the database and the previous nickName locally. Here is the updated code:
var nickNames = [String : String]()
db.collection("cities").whereField("state", isEqualTo: "CA").addSnapshotListener { snapshot, error in
guard error == nil, let snapshot = snapshot?.documentChanges else { return }
snapshot.forEach {
let document = $0.document
let documentID = document.documentID
let nickName = document.get("nickName") as? String ?? "Error"
switch $0.type {
case .added:
print("New city: \(document.data())")
nickNames[documentID] = nickName
case .modified:
print("Nickname changed from \(nickNames[documentID]) to \(nickName)")
nickNames[documentID] = nickName
case .removed:
print("City removed with nickname \(nickNames[documentID])")
nickNames.removeValue(forKey: documentID)
}
}
}
nickNames is a dictionary with key cityID and value nickName. This code is written in Swift 5.

Swift sort of array always in ascending order

i try to show a array list sorted by its Timestamp in an descending order (newest first --> highest ts first) therefore i created a downloading method and a sorting method:
func getBlogsByAuthor(){
self.allBlogs.removeAll()
for authorId in self.authorIds{
db.collection("Blogs").whereField("authorId", isEqualTo: authorId)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let ts = document.get("ts") as! Int
let title = document.get("title") as! String
let body = document.get("body") as! String
let authorId = document.get("authorId") as! String
let state = document.get("stateios") as? String
let imageUrl = document.get("imageUrl") as! String
let id = document.documentID as! String
let blogObject = BlogObject.init(title: title , body: body, imageUrl: imageUrl , authorId: authorId , state: state ?? "Null" , id: id, ts: ts )
self.allBlogs.append(blogObject)
}
self.sortDataset()
}
}
}
}
func sortDataset(){
self.allBlogs.sorted(by: { $0.ts! < $1.ts! })
self.rv.reloadData()
}
The problem is that the values are showing always the lowest ts first no matter if i change it from self.allBlogs.sorted(by: { $0.ts! < $1.ts! })
to self.allBlogs.sorted(by: { $0.ts! > $1.ts! })
You need
self.allBlogs.sort { $0.ts! < $1.ts! } // mutating sort in place
as sorted(by returns a result that you ignore it and don't re-assign it would be
self.allBlogs = self.allBlogs.sorted(by: { $0.ts! < $1.ts! })