How to cancel a firebase observe single event database call - ios

In my iOS app, a user is able to add friends by searching for there unique username.
The user types the username in a textField and I have a textFieldDidChange notification which is fired every time the text changes.
Within that method I then call the Firebase method below to check if the username exists.
func searchFor(_ username: String) {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let lowercaseUsername = username.lowercased()
let ref = FIRDatabase.database().reference()
ref.child(FirebaseDatabaseBranchNames.usernames.rawValue).child(lowercaseUsername).observeSingleEvent(of: .value, with: { [unowned self](snapshot) in
if snapshot.exists() {
if let usernameUid = snapshot.value as? String {
self.isUserAlreadyAFriend(ref, uid: uid, usernameUid: usernameUid)
}
} else {
// username doesn't exist
}
}, withCancel: nil)
}
How can I cancel this method, before performing it again?

When you attach a listener/observer, Firebase returns a handle for that observer. You can subsequently remove the listener/observer by calling ref.removeObserverWithHandle().
So assuming you want at most one observer, you can keep the reference and observer handle in a member field of you class and then use this code in the searchFor method:
if (self.searchHandle != nil) {
self.searchRef.removeObserverWithHandle(searchHandle)
}
self.searchRef = ref.child(FirebaseDatabaseBranchNames.usernames.rawValue).child(lowercaseUsername)
self.searchHandle = self.searchRef.observeSingleEvent(of: .value, with: { [unowned self](snapshot) in
if snapshot.exists() {
if let usernameUid = snapshot.value as? String {
self.isUserAlreadyAFriend(ref, uid: uid, usernameUid: usernameUid)
}
} else {
// username doesn't exist
}
}, withCancel: nil)
Be aware that you won't be saving data transfer with this though, as the most likely result is that the database client simply drops the data that it gets back from the server.

Better you add the "removeAllObservers()" after the observe single event block. It is working for me.
let ref = Database.database().reference().ref.child(XXXX).child(YYYYY)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
}else{
}
}) { (error) in
print(error.localizedDescription)
}
ref.removeAllObservers()

Related

How to check unique username in Firebase Database (swift)

I've Firebase Database where each user has own email and username. How to check unique username? I tried to make it like this, but my code doesn't work properly therefore different users can have the same username
usernameField.isHidden = false
let username:String = self.usernameField.text!
if (usernameField.text?.isEmpty == false){
ref.child("users").queryOrdered(byChild("username").queryEqual(toValue: username).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists(){
print("username exist")
}else{
ref.root.child("users").child(userID).updateChildValues(["username": username])
}
})
}
I'm a little bit newbie in Firebase I store email and username for each user like this newUserReference.setValue(["username":String(), "email" : self.emailTextField.text!]). On next view, user can type username in usernameField.text and this value will be added in Firebase Database. But if the next user (user 2) will type the same username like previous user, it must be blocked, because username should be unique
You still need to indicate what property you want to order/filter on with queryOrdered(byChild:):
if (usernameField.text?.isEmpty == false){
ref.child("users").queryOrdered(byChild:"username").queryEqual(toValue: username).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists(){
If you're trying to store your user's id on login do this when you receive a successful response to the login:
create a Shared Instance to store the ID
class userDataSource {
var id : String? // variable to store your users ID
static let sharedInstance = PageDataSource() // global access to this dataSource
private init() {}
}
Assign the id a value after successful login
func getIDFromLogin() {
if let user = Auth.auth().currentUser {
print(user.uid)
userDataSource.sharedInstance.id = user.uid
}
}
Then you can do this to view each id:
ref.child("users").observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
print(snap.key) // you can compare userDataSource.sharedInstance.id to this value
}
}
})
Or if you just want that user's data do this:
ref.child("users").child(userDataSource.sharedInstance.id!).observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
print(snap)
}
}
})
Edit to answer your question more accurately
Here is an answer more inline with your question. First thing I will recommend is for you to add a table to Firebase that only contains the usernames, and the .uid's that they belong to. You will need to first read through that table to make sure that no one else has that username, then update the table accordingly:
// This function will check through all of the usernames and return a true or false value in the completion handler
func checkUsernames(_ completion: #escaping(_ success: Bool) -> Void) {
ref.child("usernames").observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
if snap.value == username {
completion(false)
}
}
completion(true)
} else {
completion(true) // TODO: check for errors before setting completion
}
})
}
// this function will set the username values in Firebase
func storeUsername() {
let usernameRef = ref.child("usernames")
usernameRef.updateChildValues(["\(userDataSource.sharedInstance.id!)" : username])
}
}
}
Assuming you have already handled your username variable and set it's value, you will call the functions like this:
checkUsernames({ (success) in
if success {
storeUsername()
// you may also want to update your "users" table here as well
} else { print("Duplicate Username") } // handle alert or something here
})

Completion handler Firebase observer in Swift

I am making a completion handler for a function which will return a list of objects. When it return value for first time, it works well. But when any change happen into firebase database and again observe gets called, array size gets doubled up. Why it's getting doubled up?
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
And calling like this
getStadiums(){ stadiums
print(stadiums.count) // count gets doubled up after every observe call
}
The code you're using declares stadiums outside of the observer. This means any time a change is made to the value of the database reference, you're appending the data onto stadiums without clearing what was there before. Make sure to remove the data from stadiums before appending the snapshots again:
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
stadiums.removeAll() // start with an empty array
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
This line stadiumRef.observe(.value, with: { (snapshot) in ... actually adding an observer that will be called everytime your stadium data is changed.
Because you called it twice by using getStadiums(){ stadiums ..., the total observer added will be 2.
That makes the line stadiums.append(stadium) called twice in the second call.
My suggestion would be to use stadiumRef.observe() once without calling it from getStadiums().
Create a Model as below
class OrderListModel: NSObject {
var Order:String?
var Date:String?
}
Use the below code in the view controller and you should be able to see content in your tableview
func getOrdersData() {
self.orderListArr.removeAll()
let ref = Database.database().reference().child(“users”).child(user).child("Orders")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let orderObj = OrderModel()
orderObj.Order = dictionary[“Order”] as? String
orderObj.Date = dictionary[“Date”] as? String
self.orderListArr.append(orderObj)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}, withCancel: nil)
}
func ListenForChildrenAdded() {
let registerToListenTo = "YourPathHere"
ref.child(registerToListenTo).observeSingleEvent(of: .value) { (snapshot) in
let initialChildren = snapshot.childrenCount
var incrementer = 0
ref.child(registerToListenTo).observe(.childAdded, with: { (snapshot) in
incrementer += 1
print("snapshot: \(snapshot.key) #\(incrementer)")
if incrementer == initialChildren {
print("-> All children found")
} else if incrementer > initialChildren {
print("-> Child Was Added - Run Some Code Here")
}
})
}}

Popup alert not showing when a match is not found in database

I'm using this function to check to see if a certain handle exists in my database. It works fine for the most part - if the handle exists, it updates the table view to display that user. However if there is no match for the handle entered, my alert view is not showing up.
// Search DB for matching handles
#IBAction func searchHandleButtonPressed(_ sender: Any) {
if let handleToSearch = handleSearchTextField.text?.lowercased() {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("users").queryOrdered(byChild: "lowercaseHandle").queryEqual(toValue: handleToSearch).observeSingleEvent(of: .value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject] {
for each in snapDict{
let key = each.key
let handle = each.value["handle"] as! String
self.returnedHandles.removeAll()
self.returnedHandles.append(handle)
let pic = each.value["profilePicture"] as! String
self.returnedUsersProfilePic = pic
self.returnedUsersUID = key
if handle.lowercased() != handleToSearch {
self.noHandleFoundAlert()
}
if handle != "" {
DispatchQueue.main.async {
self.getFriendsInfo()
self.tableView.reloadData()
}
}
}
}
}, withCancel: {(Err) in
// print(Err.localizedDescription)
})
}
}
I put the alert in the loop as:
if handle.lowercased() != handleToSearch {
self.noHandleFoundAlert()
}
But obviously this isn't the correct approach as it isn't working. If I enter a random string of characters, or a handle that I know isn't in use, the alert doesn't come up. Where else would I put the alert so it will show up if there's no match?
The snapshot will be converted to an empty dictionary with your current code.
Before converting the snapshot.value to a Dictionary, check if it exists with snapshot.exists(): https://firebase.google.com/docs/reference/ios/firebasedatabase/api/reference/Classes/FIRDataSnapshot#-exists
You first have to check if the query you made, has data. So you have a
.hasChild("handle")
it returns a boolean, so when is true you do the loop, else notified user.

Retrieving data with Firebase database in iOS

In my app I have a simple user base that looks like this:
What I'm trying to do is to simply fetch this list once, to check wether a username is valid when a new user signs up with a new username.
The thing is that the only ways I found to retrieve data utilize some sort of observer methods, which are not good for me.
The logic I'm trying to achieve (with the retrieving method that doesn't work) :
// When user tries to sign up with a new username
let username = nicknameField.text?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
self.usersRef.observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
let dict = snapshot.value as! NSDictionary
for val in dict.allValues {
if username == val as! String {
// Present alert
return
}
}
}
self.usersRef.child(username).setValue(username) { (error, dbRef) in
if error == nil {
// Continue
}
}
How can I simply just fetch the list of users once?
Thanks in advance!
I had to change the observeEventType method to observeSignleEventOfType.
I have also updated my code to make it work (regardless):
self.usersRef.observeSingleEventOfType(.Value) { (snapshot: FIRDataSnapshot) in
let dict = snapshot.value as! NSDictionary
for val in dict.allValues {
if username == val as! String {
// Present alert
return
}
else {
self.usersRef.child(username).setValue(username) { (error, dbRef) in
if error == nil {
// Continue
}
}
}

Check if user exist with firebase 3.0 + swift

I have a app that after the user use Firebase auth it store the data on the Firebase database. Before storing the data, I want to check if the username the user give already exist in the database. So if it not exist I could give the user this unique username(like every user have a unique username). So I have a textField where the user enter his username, and then press Next. Then the app should check if the username exist or not, and tell the user if he need to change it.
So the code I used to check if the username exist:
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if snapshot.hasChild(self.usernameTextField.text!){
print("user exist")
}else{
print("user doesn't exist")
}
})
So every time the next button is pressed, this code is called. The problem with this is that the result always remain the same as the first search (even after the textField value change).
For example, if I search Jose, and Jose exist in my database so is going to print "user exist". But when I change the textField to name that don't exist, it still show "user exist".
I figured out I need to change the .Value to FIRDataEventType.Value
if (usernameTextField.text?.isEmpty == false){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observeSingleEventOfType(FIRDataEventType.Value, withBlock: { (snapshot) in
if snapshot.hasChild(self.usernameTextField.text!){
print("true rooms exist")
}else{
print("false room doesn't exist")
}
})
struct ModelUser {
var id: String
var name: String
init(data: DataSnapshot) {
// do init stuff
}
}
func isUserRegistered(with id: String, completion: #escaping (_ exists: Bool, _ user: ModelUser?) -> ()) {
DatabaseReference.users.child(id).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
// user is already in our database
completion(true, ModelUser(data: snapshot))
} else {
// not in database
completion(false, nil)
}
}
}
This worked for me in a similar situation as yours. You can also go the Rx way like this.
enum CustomError: Error {
case userNotRegistered
var localizedDescription: String {
switch self {
case .userNotRegistered:
return "Dude is not registered..."
}
}
}
func isUserRegistered(with id: String) -> Observable<(ModelUser)> {
let reference = DatabaseReference.users.child(id)
return Observable<ModelUser>.create({ observer -> Disposable in
let listener = reference.observe(.value, with: { snapshot in
if snapshot.exists() {
observer.onNext(ModelUser(data: snapshot))
observer.onCompleted()
} else {
observer.onError(CustomError.userNotRegistered)
}
})
return Disposables.create {
reference.removeObserver(withHandle: listener)
}
})
}
The key in both cases is using the .exists() method of the snapshot.

Resources