Swift autocomplete firebase searching - swift

I have created an application where users can search for posts (user-generated) within the app. Recently it has gotten to the point where I can't load all the users at once without it crashing.
The old function would simply load the entire post node and then filter as shown below. Is it possible to replicate something similar to this with a lot more data?
self.posts.filter { (post) -> Bool in
return
post.title.lowercased().contains(searchText.lowercased())
}
I have created a new function from reading the firebase documentation. Which is intended to handle more data.
New function
var users = [User]()
fileprivate func fetchUsers(username: String) {
let ref = Database.database().reference().child("users").queryOrdered(byChild: "username").queryStarting(atValue: username).queryEnding(atValue: "\(username)\\uf8ff")
ref.observeSingleEvent(of: .value) { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any]
else { return }
dictionaries.forEach({ (key, value) in
if key == Auth.auth().currentUser?.uid {
return
}
guard let userDictionary = value as? [String: Any] else { return }
let user = User(uid: key, dictionary: userDictionary)
self.users.append(user)
})
self.users.sort(by: { (user1, user2) -> Bool in
return user1.username.compare(user2.username) == .orderedAscending
})
self.collectionView?.reloadData()
}
}
Firebase database
"users" : {
"0Udc81ELBJP7UpYiESqPj5gLDx9A" : {
"username" : "First_User"
},
"13ThJZlpkgUCcZ4zaRzzaAHFjat2" : {
"username" : "Test_User"
}
},
This function, however, is lethargic and doesn't autocomplete as it uses to (similar to how Instagram does theirs). You need to type full words into the search bar before anything comes up.
I assume there is a way to have an autocompleted search bar as it's stock standard on most apps these days. Any help fixing this is appreciated.

My guess is that you're calling fetchUsers each time the user changes the text in the search box.
In that case you'll need to clear the previous contents from the list of users whenever fetchUsers runs. So something like:
func fetchUsers(username: String) {
let ref = Database.database().reference().child("users").queryOrdered(byChild: "username").queryStarting(atValue: username).queryEnding(atValue: "\(username)\\uf8ff")
ref.observeSingleEvent(of: .value) { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any]
else { return }
self.users.removeAll() // clear all previous results
dictionaries.forEach({ (key, value) in
if key == Auth.auth().currentUser?.uid {
return
}
guard let userDictionary = value as? [String: Any] else { return }
let user = User(uid: key, dictionary: userDictionary)
self.users.append(user)
})
self.users.sort(by: { (user1, user2) -> Bool in
return user1.username.compare(user2.username) == .orderedAscending
})
self.collectionView?.reloadData()
}
}

Related

Retrieving Data From Firebase Auto Id's - Firebase Swift

I am wanting to capture all the values in my childByAutoId in firebase. Essentially, it stores all the items that a person has shortlisted. However, I do not seem to be capturing this, and I assume it is because I am not calling the snapshot correctly to factor the auto id's.
Database:
userID
-> Favourited
-> Auto Id
-> itemName: x
-> Auto Id
-> itemName: x
-> Auto Id
-> itemName: x
Code:
func retrieveItems() {
guard let userId = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("users/\(userId)/Favourited")
ref.observe(.value, with: { (snapshot) in
if snapshot.childrenCount>0 {
self.favUsers.removeAll()
for likes in snapshot.children.allObjects as! [DataSnapshot] {
let likesObject = likes.value as? [String: AnyObject]
let itemName = likesObject!["itemName"]
let likesList = Names(id: likes.key, itemName: itemName as! String?)
self.favUsers.append(likesList)
}
} else {
print("not yet")
}
})
self.favList.reloadData()
}
Could someone have a look and let me know what I may be doing wrong? Thank you!
This happens because Firebase loads data asynchronously, and right now you're calling reloadData before the self.favUsers.append(likesList) has ever run.
The call to reloadData needs to be inside the close/completion handler that is called when the data comes back from Firebase:
ref.observe(.value, with: { (snapshot) in
if snapshot.childrenCount>0 {
self.favUsers.removeAll()
for likes in snapshot.children.allObjects as! [DataSnapshot] {
let likesObject = likes.value as? [String: AnyObject]
let itemName = likesObject!["itemName"]
let likesList = Names(id: likes.key, itemName: itemName as! String?)
self.favUsers.append(likesList)
}
self.favList.reloadData() // 👈 Move this here
} else {
print("not yet")
}
})
I also recommend checking out some of these answers asynchronous data loading in Firebase.

Multiple if let statements in Swift and Firebase

I'm trying to compare my responses with other people's responses in the firebase database. My script currently has 2 if statements saying if it's my response, record my answers and then use that to compare against other responses, but it doesn't register my second if statement.
let responsesReference = Database.database().reference().child("responses")
responsesReference.observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in
guard let json = snapshot.value as? [String: Any] else { return }
do {
var similarities = [Similarity]()
for answerElement in json {
if self.currentUser.uid == answerElement.key,
let myanswer = answerElement.value as? [String: Any] {
if self.currentUser.uid != answerElement.key, //DOES NOT REGISTER
let otheranswer = answerElement.value as? [String: Any] {
let percentage = myanswer.similarity(with: otheranswer)
similarities.append(
Similarity(name: answerElement.key, percentage: percentage, answer: otheranswer)
)
}
}
}
self.similarities = similarities.sorted(by: { (a, b) -> Bool in
return a.percentage > b.percentage
})
self.tableView.reloadData()
}
Here's your code properly formatted (I copy and pasted it with no changes other than formatting it)
do {
var similarities = [Similarity]()
for answerElement in json {
if self.currentUser.uid == answerElement.key, let myanswer = answerElement.value as? [String: Any] {
if self.currentUser.uid != answerElement.key, let otheranswer = answerElement.value as? [String: Any] {
let percentage = myanswer.similarity(with: otheranswer)
similarities.append( Similarity(name: answerElement.key, percentage: percentage, answer: otheranswer) )
}
}
Take a look here
if self.currentUser.uid == answerElement.key
and note the next if is nested inside that one
if self.currentUser.uid == answerElement.key
if self.currentUser.uid != answerElement.key
If those two vars are equal in the outside if, they will be equal with the inside if as well so the second check will always fail.
The generic solution is to use and else with your if
if self.currentUser.uid == answerElement.key {
let myanswer = answerElement.value as? [String: Any] <- OPTIONAL!
//do something because they are equal
} else {
let otheranswer = answerElement.value as? [String: Any] <- OPTIONAL!
// do something else because they are NOT equal
}
also note that you've got some optionals in that code and if those go to nil your app will either crash or silently fail with no indication as to why.
The logic isn't exactly clear in this code
let percentage = myanswer.similarity(with: otheranswer)
as each time through the loop
for answerElement in json {
}
there will only be one answer in an answerElement. e.g. there won't be a myAnswer and otherAnswer, there will only be theAnswer. Perhaps there should be a comparison to the prior answer from the loop; I'll expand on that
Here's an example based on reading in all users, getting the answer for this user, removing the user from the results and then comparing that to other users answers. Assume users uid's are used at the key to each user node (which also contains an answer they provides) and we know the auth'd users uid.
let thisUsersUid = "uid_1"
let usersRef = self.ref.child("users") //self.ref points to MY firebase
usersRef.observeSingleEvent(of: .value, with: { snapshot in
var allUsersSnap = snapshot.children.allObjects as! [DataSnapshot]
guard let index = allUsersSnap.firstIndex { $0.key == thisUsersUid } else {
print("no user: \(thisUsersUid) found")
return
}
let thisUserSnap = allUsersSnap[index] //keep this so it can be compared later
allUsersSnap.remove(at: index)
let thisUsersAnswer = thisUserSnap.childSnapshot("answer").value as? String ?? "No Answer"
for otherUserSnap in allUsersSnap {
let otherUsersAnswer = otherUserSnap.childSnapshot("answer").value as? String ?? "No Answer"
if orherUsersAnswer == thisUsersAnswer {
//do something because the answers match
}
}
})

Checking a null refrence Firebase swift

I have a application where I get values from a reference. The issue I have now is at times the reference might not the present and if the refrence is not present, the user is just suppose to be in idle state. How ever, I do not know how to achieve this task as the particular refrence could be absent from the firebase.
Below is my code for checking the ref data
func refreshActiveTrip() -> Observable<Trip> {
guard let trip = getCurrentTrip() else {
return Observable.error(RxError.noElements)
}
tripRef = Database.database().reference(forTransportChampionId: (getChampion()?.id)!, tripId: trip.id!)
return Observable<Trip>.create({ (observer) -> Disposable in
let disposable = Disposables.create {
self.tripRef?.removeAllObservers()
}
self.tripRef?.observe(.value, with: { (snapshot) in
if let data = snapshot.value as? [String: AnyObject] {
let trip = Trip(dictionary: data as NSDictionary)
self.saveCurrentTrip(trip)
if !disposable.isDisposed {
observer.onNext(trip)
}
}
})
return disposable
})
}
tripRef could be null i.e it does not exsit, how can i check this and an idle value
How about using the exists()
FIRDatabase.database().reference()
ref.child("the_path").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
print("It exists!!")
}else{
print("Nope, doesn't exist!")
}
})
Firebase Doc Reference

Cant fetch SavedPost

My app has an option to save posts for users to watch later. The code is:
func savedPost(for cell: FirstView) {
guard let indexPath = collectionView.indexPath(for: cell) else { return }
var post = self.posts[indexPath.item]
guard let currentuserId = Auth.auth().currentUser?.uid else { return }
let targetUid = post.user.uid
guard let postId = post.id else { return }
let ref = Database.database().reference().child("save_post").child(currentuserId).child(postId)
if post.hasSaved {
ref.removeValue { (err, _) in
if let _ = err {
showErr(info: NSLocalizedString("failtoUnsave", comment: ""), subInfo: tryLater)
return
}
post.hasSaved = false
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
} else {
let values = ["userId": targetUid]
ref.updateChildValues(values) { (err, ref) in
if let _ = err {
showErr(info: NSLocalizedString("failtoSave", comment: ""), subInfo: tryLater)
}
post.hasSaved = true
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
}
}
With this code my firebase database in "save_post" has -> currentUseruId -> postid -> postUserId.
On ProfileController users can view saved Posts from "savedPost" Tab. The code is:
var savedPosts = [Post]()
fileprivate func fetchSaved() {
var userIds = [String]()
var postIds = [String]()
guard let uid = self.user?.uid else { return }
let getIDsRef = Database.database().reference().child("save_post").child(uid)
let query = getIDsRef.queryOrderedByKey()
query.observeSingleEvent(of: .value) { (snapshot) in
let dictionary = snapshot.value as? [String: Any]
dictionary?.forEach({ (key,value) in
guard let dic = value as? [String: String] else { return }
postIds.append(key)
userIds.append(dic["userId"] ?? "")
})
var i = 0
while i < userIds.count {
self.fetchPostsWithUserIDPostID(userID: userIds[i], postID: postIds[i])
i += 1
}
}
}
fileprivate func fetchPostsWithUserIDPostID(userID: String, postID: String) {
let getPostRef = Database.database().reference().child("video_list")
getPostRef.child(userID).child(postID).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
let ref = Database.database().reference().child("users").child(userID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let user = User(uid: userID, dictionary: dict)
var post = Post(user: user, dictionary: dictionary)
post.id = postID
guard let currentUserUID = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("likes").child(postID).child(currentUserUID).observeSingleEvent(of: .value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
post.hasSaved = true
self.savedPosts.append(post)
self.savedPosts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView.reloadData()
})
})
})
}
However, when I click "savedPost" tab, there is no post shown. I don't know where my mistake is. I have all the necessary code under all override func collectionView(....). I believe the error should come from the code listed above. I am sincerely looking for help to resolve this issue. Thanks.
There could be a number of things going on here. It would be good to throw some print statements in there to make sure that 1) the data you're getting back from the database looks like what you expect, and 2) that you're properly parsing it into Post objects. Do you have your cells defined properly for your CollectionView? Also, I don't see where you are defining the data source for the CollectionView.

Firebase one of two observers not working

I have two observers, the second observer is dependent on the first observers value. I can't seem to get the first observer to work, I am not getting any errors on Xcode. The first function has to check the Users profile for information and then use that information to search for different information in the database. Here is my code:
func loadposts() {
ref = Database.database().reference()
let trace = Performance.startTrace(name: "test trace")
trace?.incrementCounter(named:"retry")
let userID = Auth.auth().currentUser?.uid
print(userID!)
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let one1 = value?["Coupon Book"] as? String ?? ""
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
}) { (error) in
print(error.localizedDescription)
}
delay(0.1) {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
Database.database().reference().child("Coupons").child(self.bogus.string(forKey: "bogus")!).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] as! String
let offerx = dict["offer count"] as! String
let logocomp = dict["logo"] as! String
let actchild = dict["childx"] as! String
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
self.tableview.reloadData()
print(self.posts)
}
}
}
trace?.stop()
}
Any help is appreciated.
self.bogus.string(forKey: "bogus"))" is nil because observeSingleEvent is an async method, so to get the required results you need to call the second observer inside the first observer or you can use the completion handler
You can use the completionHandler like this:
guard let uid = Auth.auth().currentUser?.uid else {
return
}
func firstObserverMethod(completionCallback: #escaping () -> Void) {
ref.child("Users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
if let value = snapshot.value as? [String: Any] {
let one1 = value["Coupon Book"] as? String
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
completionCallback()
}
}) { (error) in
print(error.localizedDescription)
}
}
Now using the above method:
firstObserverMethod {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
guard let bogusString = self.bogus.string(forKey: "bogus") else {
print("bogus is not set properly")
return
}
Database.database().reference().child("Coupons").child(bogusString).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] ?? ""
let offerx = dict["offer count"] ?? ""
let logocomp = dict["logo"] ?? ""
let actchild = dict["childx"] ?? ""
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
DispatchQueue.main.async {
self.tableview.reloadData()
}
print(self.posts)
}
}
}
Note: You should use optional binding to get the values from optional
Since you are using the result of the 1st observer in the reference of your 2nd observer, it's a very bad idea to add the 2nd observer right below the first observer. And adding a delay won't be a viable solution : these two calls are asynchronous, which means that the reason why you are not getting might very likely be because the 2nd observer is triggered even before the 1st has returned any data.
The solution here, would be using a completion handler, or you could just incorporate your 2nd observer inside the completion block of the 1st, to be make sure that the proper order (1st observer -> 2nd observer) will always be respected.
It would look somehow like this:
func loadposts() {
// ...
// 1st Observer here
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get your value here
guard let one1 = snapshot.childSnapshot(forPath: "Coupon Book").value as? String else { return }
// 2nd Observer here. Now you can use one1 safely:
Database.database().reference().child("Coupons").child(one1).observe(.childAdded) { (Snapshot : DataSnapshot) in
// ...
}
})
}
Now, a couple of things that you could also improve in your code, while not directly related to the question:
I would suggest you to make use of guard statements instead force-unwrapping, which may end up in crashing your app at some point.
For example, you could check whether your current user exist or not like so:
guard let currentUserID = Auth.auth().currentUser?.uid else {
return
}
// Now you can use safely currentUserID
Also, when you try to get the data out of the snapshot, it's not a good idea either, to use force-casting. You would better write it in this way:
yourRef.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let text = child.childSnapshot(forPath: "text").value as? String, let somethingElse = child.childSnapshot(forPath: "otherValue").value as? NSNumber else {
return
}
// And so on, depending of course on what you have in your database.
}