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.
Related
I am making a social app to which I am fetching some data and flushing it to the collection view. I am flushing the all the posts from firebase to the posts array. I am also fetching the user information that posted the specific image. Both the database are 2 different models. Following is my data model :
posts
|- <post_id>
|- caption
|- ImageURL
|- views
|- spot
|- spot_id
|- sender<user_id>
|- spotted(value)
|- timestamp
|- author(<user_id>)
users
|- <user_id>
|- name
Following is the way I am fetching the post data in collectionVC and storing all to posts array:
func initialiseAllPostsContent(){
FBDataservice.ds.REF_CURR_USER.child("connections/following").observe(.childAdded) { (snapshot) in
if let snapshot = snapshot.value as? String {
self.followerKeys.append(snapshot)
}
}
if uid != nil {
self.followerKeys.append(uid!)
}
FBDataservice.ds.REF_POSTS.queryOrdered(byChild: "timestamp").observe(.childAdded, with: { (snapshot) in
print("post key is ", snapshot.key)
if let postDict = snapshot.value as? Dictionary<String, Any> {
let key = snapshot.key
if let postAuthor = postDict["author"] as? String {
for user in self.followerKeys {
if postAuthor == user {
let post = Posts(postId: key, postData: postDict)
self.posts.append(post)
}
}
}
}
})
reloadCollectionViewData()
}
func reloadCollectionViewData() {
FBDataservice.ds.REF_POSTS.queryOrdered(byChild: "timestamp").observe(.value) { (snapshot) in
self.collectionView.reloadData()
}
}
//I am updating the views on the post after a method is successfull. As soon as this is called, and then if like is pressed, views flicker
func updateViews(postid: String, views: Int) {
let viewref = FBDataservice.ds.REF_POSTS.child(postid)
let newviews = views + 1
viewref.updateChildValues(["views":newviews])
}
// fetching the user data from the post data
func getAllPosts(pid: String, completion: #escaping ((String) -> ())) {
FBDataservice.ds.REF_POSTS.child(pid).observeSingleEvent(of: .value) { (snapshot) in
if let snapshot = snapshot.value as? Dictionary<String, Any> {
if let userid = snapshot["author"] as? String {
completion(userid)
}
}
}
}
func getpostAuthorData(authorId : String, completion: #escaping (User) -> ()) {
FBDataservice.ds.REF_USERS.child(authorId).observeSingleEvent(of: .value) { (snapshot) in
if let snapshot = snapshot.value as? Dictionary<String, Any> {
if let userCredential = snapshot["credentials"] as? Dictionary<String, Any> {
completion(User(userid: authorId, userData: userCredential))
}
}
}
}
This is how I am assigning data in my cellForItemAtIndexPath
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
self.posts.sort(by: { $0.timestamp < $1.timestamp})
let post = posts[indexPath.row]
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? SpotGroundCell {
cell.configureCellData(post: post)
getAllPosts(pid: post.postId) { (userid) in
self.getpostAuthorData(authorId: userid, completion: { (userdata) in
cell.configUserData(user: userdata)
})
}
return cell
} else {
return SpotGroundCell()
}
}
The code in my cell :
//Consider this as likes. I allow users to like multiple times. Once the model is loaded, it fetches all the spots according to the timestamp and then siplayer the most recent ones. Even this is doesn't display according to the current image and flickers. I replicate previous cell values even though I am refreshing the view.
var currentUserSpots = [Spot]() {
didSet {
self.currentUserSpots.sort(by: { $0.timestamp < $1.timestamp})
if !self.currentUserSpots.isEmpty {
self.emotionImage.image = UIImage(named: (self.currentUserSpots.first?.spotted)!)
self.emotionImage.alpha = 1
} else {
self.emotionImage.image = UIImage(named: "none")
self.emotionImage.alpha = 0.5
}
}
}
func configUserData(user: User) {
self.user = user
self.name.text = self.user.name
}
func configureCellData(post: Posts) {
print("Config is now called")
self.posts = post
self.caption.text = posts.caption
FBDataservice.ds.REF_POSTS.child(post.postId).child("spot").queryOrdered(byChild: "senderID").queryEqual(toValue: uid!).observeSingleEvent(of: .childAdded) { (snapshot) in
if let spotData = snapshot.value as? Dictionary<String, Any> {
let spot = Spot(id: snapshot.key, spotData: spotData)
if spot.spotted != nil {
self.currentUserSpots.append(spot)
}
}
}
}
Now whenever I am making a change or an event which updates the database(like updating a view). I see a flicker in the user object entities(such as name etc). That event also kills other processes and Notification Observers.
I scrapped the internet for the solutions, but by far just was able to find one, which doesn't solve my problem.
Any help will be greatly appreciated. I am really not sure where am I going wrong.
Whenever there is a change under REF_POSTS you right now:
delete all data from the view
re-add all data (including the change) to the view
Given that most changes will only affect one item in the list, you're making your view to N-1 more than is needed. This causes the flicker.
To solve this problem, you should listen to more granular information from the database. Instead of observing .value, add a listener for .childAdded. The completion block for this listener will be triggered whenever a new child is added, at which point you can just add the new child to your view.
FBDataservice.ds.REF_POSTS.queryOrdered(byChild: "timestamp").observe(.childAdded, with: { (snap) in
if let postDict = snap.value as? Dictionary<String, Any> {
let key = snap.key
if let postAuthor = postDict["author"] as? String {
for user in self.followerKeys {
if postAuthor == user {
let post = Posts(postId: key, postData: postDict)
self.posts.append(post)
}
}
}
}
})
As a bonus .childAdded also immediately fires for all existing child nodes, so you don't need the observer for .value anymore. I like keeping it myself though. As Firebase guarantees that it fires .value after all corresponding child* events, the .value event is a great moment to tell the view that all changes came in.
FBDataservice.ds.REF_POSTS.queryOrdered(byChild: "timestamp").observe(.value, with: { (snapshot) in
self.collectionView.reloadData()
})
You'll need a few more things for a complete implementation:
You should also observe .childChanged, .childMoved and childRemoved to handle those types of changes to the database.
Since a child may be added (or moved) anywhere in the list, you should actually use observe(_, andPreviousSiblingKey: ) to be able to put the item in the right spot in the list.
First of all, I am new in this, so please do not make fun of me :)
Basically, I am trying to show and Image of a product but if the client refuses the product this item will not appear on his account. That is why I am creating another table Rejected (setAcceptedOrRejected) where I put the ID of the product and the Id of the client so I wont see the item he rejected before.
What I tried here it was to get the List (Good) with all the items and the (Bad) with the rejected items. Then compare it to display the picture of the item again.
My problem is that I want to show only 1 picture at the time, if the client refuses then it will show the next one and so on but it wont show that picture again.
I hope you can really help me with this one.
Thank you
func updateImage() {
createListProductsBad ()
var badnot = ""
for bad2 in listProductsBad{
badnot = bad2
}
Database.database().reference().child("Products").child(bad2)queryOrderedByKey().observe(.childAdded, with: { snapshot in
let userInfo = snapshot.value as! NSDictionary
let storageRef = Storage.storage().reference(forURL: profileUrl)
storageRef.downloadURL(completion: { (url, error) in
do {
let data = try Data(contentsOf: url!)
let image = UIImage(data: data as Data)
self.productPhoto.image = image
}
catch _ {
print("error")
}
})
})
}
func setAcceptedOrRejected() {
let notThankyou = [ "ProductID": ProductId,
"UserID": userUID
] as [String : Any]
self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.refProducts.child("Rejected").childByAutoId().setValue(notThankyou)
}
func createListProductsGood () {
Database.database().reference().child("Products").queryOrderedByKey().observe(.childAdded, with: { snapshot in
if !snapshot.exists() { return }
let userInfo = snapshot.value as! NSDictionary
let goodID = String(snapshot.key)
for prod in self.listProductsBad{
if (prod == goodID){
print("Not good **********************")
}else{
if (goodID != "" ){
self.listProductsGood.append(prod)
}
}
}
})
}
func createListProductsBad () {
Database.database().reference().child("Rejected").queryOrderedByKey().observe(.childAdded, with: { snapshot in
let userInfo = snapshot.value as! NSDictionary
let currentID = userInfo["UserID"] as! String
let badProduct = userInfo["ProductID"] as! String
if (self.userUID == currentID ){
self.listProductsBad.append(badProduct)
}
})
}
}
//These can also be swift's dictionaries, [String: AnyObject] or possibility arrays if done correctly. All depends on your style of programming - I prefer NSDictionaries just because.
let availableKeys: NSMutableDictionary = [:]
let rejectedKeys: NSMutableDictionary = [:]
//Might be a better way for you. Depends on what you are looking for.
func sortItems2() -> NSMutableDictionary{
for rejKey in rejectedKeys.allKeys{
//Removes if the rejected key is found in the available ones
availableKeys.remove(rejKey)
}
return availableKeys
}
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.
}
I have this code below in order to retrieve a list of restaurants and their data. However, it's not storing the data, and every time I try to return the array it returns nil. But if I print it, prints the data. Any suggestions?
func getRestaurants()-> Array<Restaurant>{
var baruri = [Restaurant]()
dataBaseRef.child("AthensRestaurants/Restaurants").observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print(snap)
if let restaurantData = snap.value as? Dictionary<String, AnyObject> {
let restaurant = Restaurant(restaurantData: restaurantData)
baruri.append(restaurant)
print(baruri)
}
}
}
})
return baruri
}
The firebase observe is an asynchronous callback function, so it will run after it is finished. In other words, your return baruri will always runs before the value got back. You can use completion handler to get the value you want.
var restaurants = [Restaurant]()
func getRestaurants(completion: #escaping (Array<Restaurant>) -> Void){
var baruri = [Restaurant]()
dataBaseRef.child("AthensRestaurants/Restaurants").observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print(snap)
if let restaurantData = snap.value as? Dictionary<String, AnyObject> {
let restaurant = Restaurant(restaurantData: restaurantData)
baruri.append(restaurant)
print(baruri)
completion(baruri)
}
}
}
})
}
// Call this function with call back
getRestaurants { restaurants in
self.restaurants = restaurants
}
I am using the subscript in the following code incorrectly for this Firebase data pull, but I can't figure out what I am doing wrong. I get an error of Ambiguous use of subscript for the let uniqueID = each.value["Unique ID Event Number"] as! Int line.
// Log user in
if let user = FIRAuth.auth()?.currentUser {
let uid = user.uid
// values for vars sevenDaysAgo and oneDayAgo set here
...
let historyRef = self.ref.child("historyForFeedbackLoop/\(uid)")
historyRef.queryOrdered(byChild: "Unix Date").queryStarting(atValue: sevenDaysAgo).queryEnding(atValue: oneDayAgo).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("user data not found")
}
else {
if let snapDict = snapshot.value as? [String:AnyObject] {
for each in snapDict {
// Save the IDs to array.
let uniqueID = each.value["Unique ID Event Number"] as! Int
self.arrayOfUserSearchHistoryIDs.append(uniqueID)
}
}
else{
print("SnapDict is null")
}
}
})
}
I tried to applying what I learned from this post, but I couldn't figure out what I am missing because I thought I was letting the compiler know what type of dictionary it is with the "as? [String:AnyObject]"
Any thoughts or ideas would be greatly appreciated!
My preferred way of dealing with data is to unwrap the FIRDataSnapshot as late as possible.
ref!.observe(.value, with: { (snapshot) in
for child in snapshot.children {
let msg = child as! FIRDataSnapshot
print("\(msg.key): \(msg.value!)")
let val = msg.value! as! [String:Any]
print("\(val["name"]!): \(val["message"]!)")
}
})
Taking Frank's feedback into account, here is the actual working code I used that follows that approach in case it's helpful.
// Log user in
if let user = FIRAuth.auth()?.currentUser {
let uid = user.uid
// values for vars sevenDaysAgo and oneDayAgo set here
...
let historyRef = self.ref.child("historyForFeedbackLoop/\(uid)")
historyRef.queryOrdered(byChild: "Unix Date").queryStarting(atValue: sevenDaysAgo).queryEnding(atValue: oneDayAgo).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("user data not found")
}
else {
for child in snapshot.children {
let data = child as! FIRDataSnapshot
let value = data.value! as! [String:Any]
self.arrayOfUserSearchHistoryIDs.append(value["Unique ID Event Number"] as! Int)
}
}
})
}