Firebase duplicate cell - swift

Is possible to avoid duplicating of cell with content of the same name during import customer from database to tableview? In my example if customer Ben Smith has two children with values I want only one cell with his name.
This is my database structure...
And result in tableview:
let userID = Auth.auth().currentUser!.uid
let usersDatabaseRef = Database.database().reference().child("usersDatabase").child(userID).child("Customers")
usersDatabaseRef.observe(.value, with: { snapshot in
print("there are \(snapshot.childrenCount) users")
for child in snapshot.children {
let childSnap = child as! DataSnapshot
print("user: \(childSnap.key)")
let userCustomerSnap = childSnap
for customer in userCustomerSnap.children.allObjects as! [DataSnapshot] {
let customerSnap = customer
let dict = customerSnap.value as! [String: Any]
let name = dict["Name and surname"]
let phone = dict["Phone"]
let company = dict["Company name"]
let customerID = dict["ID"]
let email = dict["Email"]
let nip = dict["Nip1"]
let postal = dict["Postal code"]
let street = dict["Street"]
let town = dict["Town"]
let myCustomer = CustomerModel(name: name as? String, phone: phone as? String, company: company as? String, customerID: customerID as? String, email: email as? String, nip: nip as? String, postal: postal as? String, street: street as? String, town: town as? String)
self.candies.append(myCustomer)
self.filteredCandies.append(myCustomer)
}
self.tableViewCustomer.reloadData()

You're adding an item to the list each time you find an order (or whatever the level under "Ben Smith" represents in your data) from that customer. So your list is a list of orders, not a list of customers.
In general in NoSQL/Firebase, it is recommended to model your database for what you want to display. So if you want a list of customers, that's what I'd store in the database. But given your data structure, you can also fix it in code:
usersDatabaseRef.observe(.value, with: { snapshot in
print("there are \(snapshot.childrenCount) users")
for child in snapshot.children {
let childSnap = child as! DataSnapshot
let myCustomer = CustomerModel(name: child.key, phone: "", company: "", customerID: "", email: "", nip: "", postal: "", street: "", town: ")
self.candies.append(myCustomer)
self.filteredCandies.append(myCustomer)
}
self.tableViewCustomer.reloadData()
})
Or alternatively, only add the new customer when their name is different from the previous order you saw:
var previousName: String = ""
usersDatabaseRef.observe(.value, with: { snapshot in
print("there are \(snapshot.childrenCount) users")
for child in snapshot.children {
let childSnap = child as! DataSnapshot
print("user: \(childSnap.key)")
let userCustomerSnap = childSnap
for customer in userCustomerSnap.children.allObjects as! [DataSnapshot] {
let customerSnap = customer
let dict = customerSnap.value as! [String: Any]
let name = dict["Name and surname"]
if name != previousName {
let phone = dict["Phone"]
let company = dict["Company name"]
let customerID = dict["ID"]
let email = dict["Email"]
let nip = dict["Nip1"]
let postal = dict["Postal code"]
let street = dict["Street"]
let town = dict["Town"]
let myCustomer = CustomerModel(name: name as? String, phone: phone as? String, company: company as? String, customerID: customerID as? String, email: email as? String, nip: nip as? String, postal: postal as? String, street: street as? String, town: town as? String)
self.candies.append(myCustomer)
self.filteredCandies.append(myCustomer)
previousName = name
}
}
}
self.tableViewCustomer.reloadData()

You really don't need that for loop. That might be what's causing you issue. The code is called for the same number of times that you have children anyway so you don't need that loop. Try the code below. If that doesn't work, you might need to try .childAdded instead of .value
guard let userID = Auth.auth().currentUser?.uid else { return }
let usersDatabaseRef = Database.database().reference().child("usersDatabase").child(userID).child("Customers")
usersDatabaseRef.observe(.childAdded, with: { snapshot in
guard let dict = snapshot.value as? [String: Any] else { return }
let name = dict["Name and surname"] as? String
let phone = dict["Phone"] as? String
let company = dict["Company name"] as? String
let customerID = dict["ID"] as? String
let email = dict["Email"] as? String
let nip = dict["Nip1"] as? String
let postal = dict["Postal code"] as? String
let street = dict["Street"] as? String
let town = dict["Town"] as? String
let myCustomer = CustomerModel(name: name, phone: phone, company: company, customerID: customerID, email: email, nip: nip, postal: postal, street: street, town: town)
self.candies.append(myCustomer)
self.filteredCandies.append(myCustomer)
DispatchQueue.main.async {
self.tableViewCustomer.reloadData()
}
}, withCancel: nil)

Related

Is it possible to read from multiple child nodes?

I want to read all three data sourcing from "Arts & Humanities" and "Beauty & Style". Is this possible?
Let ref = Database.database().reference().child("posts")
//CODE A: Pulls 2 snapshot, but doesn't display anything
let ref = Database.database().reference().child("posts").child("Arts & Humanities")
//CODE B: only pulls up the two feeds but excludes beauty and style. Vice versa
//Below is the listener code I have. This works only works with CODE B above; but ideally id like to read the post under "Beauty & Style" as well.
postsRef.observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [PostModel]()
for child in snapshot.children {
print(snapshot.childrenCount)
if let childSnapshot = child as? DataSnapshot,
let dict = childSnapshot.value as? [String:Any],
let author = dict["author"] as? [String:Any],
let uid = author["uid"] as? String,
let username = author["username"] as? String,
let fullname = author["fullname"] as? String,
let patthToImage = author["patthToImage"] as? String,
let url = URL(string:patthToImage),
let pathToImage = dict["pathToImage"] as? String,
let likes = dict["likes"] as? Int,
let postID = dict["postID"] as? String,
let message = dict["message"] as? String,
let genre = dict["genre"] as? String,
let timestamp = dict["timestamp"] as? Double {
if childSnapshot.key != lastPost?.id {
let userProfile = UserProfile(uid: uid, fullname: fullname, username: username, patthToImage: url)
let post = PostModel(genre: genre, likes: likes, message: message, pathToImage: pathToImage, postID: postID, userID: pathToImage, timestamp: timestamp, id: childSnapshot.key, author: userProfile)
tempPosts.insert(post, at: 0)
if lastPost?.id != nil {
lastPostIdChecker = lastPost!.id
}
}
}
}
return completion(tempPosts)
})

Swift, How to Get Completion Handler working as I Wish?

I am fetching user information from Firebase and my goal is to fetch a batch of users and then display them on the screen one at a time in a card stack. To do this i use a completion handler. However my code inside the completion handler runs before the fetch of all users is done.
Thank you for any help.
Here is my code. I want "fetchOneUser()" to run when "fetchAllUsers" is done:
fetchAllUsers(completion: { message in
print(message)
print("FetchOneUser")
self.fetchOneUser()
})
Here is fetchAllUser function:
func fetchAllUsers(completion: #escaping (_ message: String) -> Void){
//User or advertiser?
Database.database().reference(withPath: "Advertiser").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
myAdvertiserVar.advertiser = true
self.currentUserKind = "Advertiser"
self.otherUserKind = "Users"
}
else{
self.currentUserKind = "Users"
self.otherUserKind = "Advertiser"
}
// Fetch
let query = self.ref?.child(self.otherUserKind).queryOrdered(byChild: "email")
query?.observeSingleEvent(of: .value) {
(snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
let id = child.key
//If Already Accepted, don't fetch
Database.database().reference(withPath: self.currentUserKind).child(self.uid).child("Accepted").child(id).observeSingleEvent(of: .value, with: {(accepted) in
if accepted.exists(){
print("\(id) är redan Accepted")
}
else{
if myAdvertiserVar.advertiser == true{
let value = child.value as? NSDictionary
let username = value?["Username"] as? String
let occupation = value?["Occupation"] as? String
let age = value?["Age"] as? String
let bio = value?["Bio"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
else{
let value = child.value as? NSDictionary
let username = value?["Owner"] as? String
let occupation = value?["Location"] as? String
let age = value?["Rent"] as? String
let bio = value?["About"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
}
})
}
print(self.usersArray.count)
completion("Users list fetched")
}
})
}
You need to use DispatchGroup as the inner calls are asynchronous
func fetchAllUsers(completion: #escaping (_ message: String) -> Void){
//User or advertiser?
Database.database().reference(withPath: "Advertiser").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
myAdvertiserVar.advertiser = true
self.currentUserKind = "Advertiser"
self.otherUserKind = "Users"
}
else{
self.currentUserKind = "Users"
self.otherUserKind = "Advertiser"
}
// Fetch
let query = self.ref?.child(self.otherUserKind).queryOrdered(byChild: "email")
query?.observeSingleEvent(of: .value) {
(snapshot) in
let g = DispatchGroup()
for child in snapshot.children.allObjects as! [DataSnapshot] {
let id = child.key
//If Already Accepted, don't fetch
g.enter()
Database.database().reference(withPath: self.currentUserKind).child(self.uid).child("Accepted").child(id).observeSingleEvent(of: .value, with: {(accepted) in
if accepted.exists(){
print("\(id) är redan Accepted")
}
else{
if myAdvertiserVar.advertiser == true{
let value = child.value as? NSDictionary
let username = value?["Username"] as? String
let occupation = value?["Occupation"] as? String
let age = value?["Age"] as? String
let bio = value?["Bio"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
else{
let value = child.value as? NSDictionary
let username = value?["Owner"] as? String
let occupation = value?["Location"] as? String
let age = value?["Rent"] as? String
let bio = value?["About"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
}
g.leave()
})
}
g.notify(queue: .main, execute: {
print(self.usersArray.count)
completion("Users list fetched")
})
}
})
}
Based on Firebase documentation:
Firebase use refrence() method to get a database refrence for the root of your real time database asynchronous.
this means that result takes more time to fetch than for loop, in this situation your for loop finishes and completion block calls and takes you out of method, then result of your request will return.
your code should look like
var firebaseDatabaseRefrence: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
Database.database().reference(withPath: self.currentUserKind)
}
func someMethod() {
self.firebaseDatabaseRefrence
.child(self.uid)
.child("Accepted")
.child(id).observeSingleEvent(of: .value, with: {(accepted) in
}

How to fetch limited amount of users from Firebase

I am doing a card stack swipe app with users similar to Tinder.
I want to fetch a batch of 10 users from Firebase. I have used queryLimited(toFirst: 10) for this but later in my function i won't fetch the users who have already been accepted(swiped right). This means that if the first 10 users from Firebase already have been accepted nobody will be fetched. I want to fetch the first 10 users who hasn't been accepted.
Does anyone have a great solution for this?
Thank you.
FetchUsers code for fetching users from Firebase:
func fetchAllUsers(completion: #escaping (_ message: String) -> Void){
//User or advertiser?
Database.database().reference(withPath: "Advertiser").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
myAdvertiserVar.advertiser = true
self.currentUserKind = "Advertiser"
self.otherUserKind = "Users"
}
else{
myAdvertiserVar.advertiser = false
self.currentUserKind = "Users"
self.otherUserKind = "Advertiser"
}
// Fetch
let query = self.ref?.child(self.otherUserKind).queryOrdered(byChild: "email").queryLimited(toFirst: 10)
query?.observeSingleEvent(of: .value) {
(snapshot) in
let g = DispatchGroup()
for child in snapshot.children.allObjects as! [DataSnapshot] {
let id = child.key
//If Already Accepted, don't fetch
g.enter()
Database.database().reference(withPath: self.currentUserKind).child(self.uid).child("Accepted").child(id).observeSingleEvent(of: .value, with: {(accepted) in
if accepted.exists(){
print("\(id) är redan Accepted")
}
else{
if myAdvertiserVar.advertiser == true{
let value = child.value as? NSDictionary
let username = value?["Username"] as? String
let occupation = value?["Occupation"] as? String
let age = value?["Age"] as? String
let bio = value?["Bio"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
else{
let value = child.value as? NSDictionary
let username = value?["Owner"] as? String
let occupation = value?["Location"] as? String
let age = value?["Rent"] as? String
let bio = value?["About"] as? String
let email = value?["email"] as? String
let user = User(id: id, username: username, occupation: occupation, age: age, bio: bio, email: email)
self.usersArray.append(user)
}
}
g.leave()
})
}
g.notify(queue: .main, execute: {
print(self.usersArray.count)
completion("Users list fetched")
})
}
})
}

Swift Retrieving Data Firebase

I'm trying to retrieve string key which I have saved it in the object
func retrieveData() {
let refAll = Database.database().reference().child("Playground")
refAll.observe(.value) { (snapshot) in
if let snapshotValue = snapshot.value as? [String:Any] {
var playgroundSnapshot = snapshotValue
let playgroundKeys = Array(playgroundSnapshot.keys)
self.playgroundArray.removeAll()
for key in playgroundKeys {
guard
let value = playgroundSnapshot[key] as? [String : Any]
else {
continue
}
let title = value["title"] as! String
let city = value["city"] as! String
let location = value["location"] as! String
let price = value["price"] as! String
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true)
self.playgroundArray.append(playground)
}
self.tabeView.reloadData()
}
}
}
and in the playgroundArray there is key for each object
keySelected = playgroundArray[indexPath.row].key
but I don't know why keySelected is nil even tho playgroundArray has objects
playgroundArray does not contain "key" . You have to set the Playground Struct. Add var key:String? and also add this to init() func.
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true)
to
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true, key: key)
self.playgroundArray.append(playground)

Creating a second failable initializer using a convenience initializer

I'm trying to create a second failable convenience initializer to use because I want to make two API calls and this second failable initializer contains all the properties I'll get from the original API unlike the first failable initializer that will contain all the original properties plus a few additional ones I've added on which I'll be posting to firebase.
class Alcohol {
var id: Int
var companyName: String
var address: String
var city: String
var state: String
var postal: String
var country: String
var phone: String
var email: String
var url: String
var checkedBy: String
var notes: String
var status: String
var statusColor: String
var identifier: NSUUID?
var barnivoreChecked: Bool?
var alcoholType: AlcoholType?
// This failable init is for the information I'll be retrieving from firebase.
init?(dictionary: [String: AnyObject], type: AlcoholType) {
guard
let id = dictionary[Constants.kID] as? Int,
let companyName = dictionary[Constants.kCompanyName] as? String,
let address = dictionary[Constants.kAddress] as? String,
let city = dictionary[Constants.kCity] as? String,
let state = dictionary[Constants.kState] as? String,
let postal = dictionary[Constants.kPostal] as? String,
let country = dictionary[Constants.kCountry] as? String,
let phone = dictionary[Constants.kPhone] as? String,
let email = dictionary[Constants.kEmail] as? String,
let url = dictionary[Constants.kURL] as? String,
let checkedBy = dictionary[Constants.kCheckedBy] as? String,
let notes = dictionary[Constants.kNotes] as? String,
let status = dictionary[Constants.kStatus] as? String,
let statusColor = dictionary[Constants.kStatusColor] as? String,
let barnivoreChecked = dictionary[Constants.kBarnivoreChecked] as? Bool else { return nil }
self.id = id
self.companyName = companyName
self.address = address
self.city = city
self.state = state
self.postal = postal
self.country = country
self.phone = phone
self.email = email
self.url = url
self.checkedBy = checkedBy
self.notes = notes
self.status = status
self.statusColor = statusColor
self.barnivoreChecked = barnivoreChecked
self.alcoholType = type
}
// This failable initializer has all the original properties I want to get from the initial API which I'll be retrieving the information from.
convenience init?(barnivoreDictionary: [String: AnyObject]) {
guard
let id = barnivoreDictionary[Constants.kID] as? Int,
let companyName = barnivoreDictionary[Constants.kCompanyName] as? String,
let address = barnivoreDictionary[Constants.kAddress] as? String,
let city = barnivoreDictionary[Constants.kCity] as? String,
let state = barnivoreDictionary[Constants.kState] as? String,
let postal = barnivoreDictionary[Constants.kPostal] as? String,
let country = barnivoreDictionary[Constants.kCountry] as? String,
let phone = barnivoreDictionary[Constants.kPhone] as? String,
let email = barnivoreDictionary[Constants.kEmail] as? String,
let url = barnivoreDictionary[Constants.kURL] as? String,
let checkedBy = barnivoreDictionary[Constants.kCheckedBy] as? String,
let notes = barnivoreDictionary[Constants.kNotes] as? String,
let status = barnivoreDictionary[Constants.kStatus] as? String,
let statusColor = barnivoreDictionary[Constants.kStatusColor] as? String else {
self.init(id: 0, companyName: "", address: "", city: "", state: "", postal: "", country: "", phone: "", email: "", url: "", checkedBy: "", notes: "", status: "", statusColor: "", alcoholType: alcoholType! )
return nil
}
self.id = id
self.companyName = companyName
self.address = address
self.city = city
self.state = state
self.postal = postal
self.country = country
self.phone = phone
self.email = email
self.url = url
self.checkedBy = checkedBy
self.notes = notes
self.status = status
self.statusColor = statusColor
self.alcoholType = nil
}
init(id: Int, companyName: String, address: String, city: String, state: String, postal: String, country: String, phone: String, email: String, url: String, checkedBy:String, notes: String, status: String, statusColor: String, barnivoreChecked: Bool = true, alcoholType: AlcoholType) {
self.id = id
self.companyName = companyName
self.address = address
self.city = city
self.state = state
self.postal = postal
self.country = country
self.phone = phone
self.email = email
self.url = url
self.checkedBy = checkedBy
self.notes = notes
self.status = status
self.statusColor = statusColor
self.identifier = NSUUID()
self.barnivoreChecked = barnivoreChecked
self.alcoholType = alcoholType
}
}
Unfortunately I get an error stating:
"self" used before self.init call.
And if I do try to just use self.init(), I get the error:
Cannot invoke 'Alcohol.init' with no arguments.
Any help or suggestions would be much appreciated.
In Swift 3, a convenience initializer must call a designated initializer in the same class before any attempt to access self is made in the convenience initializer.
I suggest you change your convenience initializer to be like this:
convenience init?(barnivoreDictionary: [String: AnyObject]) {
guard
let id = barnivoreDictionary[Constants.kID] as? Int,
let companyName = barnivoreDictionary[Constants.kCompanyName] as? String,
let address = barnivoreDictionary[Constants.kAddress] as? String,
let city = barnivoreDictionary[Constants.kCity] as? String,
let state = barnivoreDictionary[Constants.kState] as? String,
let postal = barnivoreDictionary[Constants.kPostal] as? String,
let country = barnivoreDictionary[Constants.kCountry] as? String,
let phone = barnivoreDictionary[Constants.kPhone] as? String,
let email = barnivoreDictionary[Constants.kEmail] as? String,
let url = barnivoreDictionary[Constants.kURL] as? String,
let checkedBy = barnivoreDictionary[Constants.kCheckedBy] as? String,
let notes = barnivoreDictionary[Constants.kNotes] as? String,
let status = barnivoreDictionary[Constants.kStatus] as? String,
let statusColor = barnivoreDictionary[Constants.kStatusColor] as? String else {
return nil
}
self.init(id: id, companyName: companyName, address: address, city: city, state: state, postal: postal, country: country, phone: phone, email: email, url: url, checkedBy: checkedBy, notes: notes, status: status, statusColor: statusColor, alcoholType: alcoholType)
}
Note that you don't need to call self.init... inside the guard.