Is it possible to read from multiple child nodes? - swift

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)
})

Related

How to know which initializer to use for reading data(Firebase)

I've got two initializers:
struct UserInfo{
let ref: DatabaseReference?
let key: String
let firstName: String
let lastName: String
let username: String
let pictureURL : String?
let admin : Bool
init(firstName: String, lastName:String,username:String,pictureURL:String?,admin:Bool, key:String = "" ){
self.ref = nil
self.key = key
self.firstName = firstName
self.lastName = lastName
self.username = username
self.pictureURL = pictureURL
self.admin = admin
}
init?(snapshot:DataSnapshot){
guard let value = snapshot.value as? [String:AnyObject],
let firstName = value["firstName"] as? String,
let lastName = value["lastName"] as? String,
let username = value["userName"] as? String,
let profilePic = value["pictureURL"] as? String,
let admin = value["isAdmin"] as? Bool
else {return nil}
self.ref = snapshot.ref
self.key = snapshot.key
self.firstName = firstName
self.lastName = lastName
self.username = username
self.pictureURL = profilePic
self.admin = admin
}
func toAnyObject()-> Any{
return [
"firstName": firstName,
"lastName": lastName,
"username": username,
"pictureURL":pictureURL as Any,
"isAdmin": admin
]
}
}
For reading most recent data I use this method combined with first init and it works:
let completed =
DataObjects.infoRef.child(uid!).observe(.value){ snapshot,error in
var newArray: [UserInfo] = []
if let dictionary = snapshot.value as? [String:Any]{
let username = dictionary["username"] as! String
let firstName = dictionary["firstName"] as! String
let lastName = dictionary["lastName"] as! String
let profilePic = dictionary["pictureURL"] as? String
let admin = dictionary["isAdmin"] as! Bool
let userInformation = UserInfo(firstName: firstName, lastName:
lastName, username: username,pictureURL: profilePic, admin: admin)
newArray.append(userInformation)
print(newArray)
completion(.success(newArray))
print(newArray)
}
Why and when do I need to use second init??
In Firebase tutorial on raywenderlich.com we gat example about: Synchronizing Data to the Table View using second init:
let completed = ref.observe(.value) { snapshot in
// 2
var newItems: [GroceryItem] = []
// 3
for child in snapshot.children {
// 4
if
let snapshot = child as? DataSnapshot,
let groceryItem = GroceryItem(snapshot: snapshot) {
newItems.append(groceryItem)
}
}
// 5
self.items = newItems
self.tableView.reloadData()
But my method works the same with first init.
The question is really asking about two things that functionally work the same.
In one case the snapshot is being "broken down" into its raw data (strings etc) within the firebase closure
DataObjects.infoRef.child(uid!).observe(.value){ snapshot,error in
let username = dictionary["username"] as! String
let firstName = dictionary["firstName"] as! String
let lastName = dictionary["lastName"] as! String
let userInformation = UserInfo(firstName: firstName, lastName: lastName...
and then passing that raw data to the struct. That object is then added to the array
In the second case the snapshot itself is passed to the struct
init?(snapshot:DataSnapshot) {
guard let value = snapshot.value as? [String:AnyObject],
and the snapshot is broken down into it's raw data within the object.
The both function the same.
It's a matter of readability and personal preference. Generally speaking having initializers etc within an object can make the code a bit more readable, the object more reusable and less code - see this pseudo code
DataObjects.infoRef.child(uid!).observe(.value){ snapshot, error in
let user = UserInfo(snapshot)
self.newArray.append(user)
})
That's pretty tight code.
Imagine if there were 10 places you wanted to access those objects within your app. In your first case, that code would have to be replicated 10 times - which could be a lot more troubleshooting. In my example above, the object itself does the heavy lifting so accessing them requires far less code.
Two other things. You may want to consider using .childSnapshot to access the data within a snapshot instead of a dictionary (either way works)
let userName = snapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
and please avoid force unwrapping optional vars
child(uid!)
as it will cause unstable code and random, unexplained crashes. This would be better
guard let uid = maybeUid else { return } //or handle the error

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)

Firebase duplicate cell

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)