Trying to get data from firebase Swift - ios

Ok, I have a function that has to find the average of the ratings, so I store the total amount of ratings and a total sum of ratings in my firebase (which works fine). I am trying to retrieve the data, but it seems that it doesn't even enter the codeblock of the .observeSingleEvent I am using the same approach when trying to update the values, which means I get them and I add the new rating to them and then I use the code below to update the values:
let ratingObject = [
"uid" : (user?.uid)! as String,
"totalRatings" : newRating as Int,
"amountOfRatings" : newAmountOfRating as int
] as [String : Any]
dbRef.setValue(ratingObject)
It doesn't give an error and I am just lost
I tried to do it based on this tutorial: https://www.youtube.com/watch?v=JV9Oqyle3iE
The answers given in this thread : how parsing Firebase FDatasnapshot json data in swift are just crashing the app
private func FindAverage(uid: String) -> Int {
var totalRatings = 0
var amountOfRatings = 1
let dbRef = Database.database().reference().child("ratings").child(uid)
dbRef.observeSingleEvent(of: .value, with: { snapshot in
let dict = snapshot.value as? [String:Any]
totalRatings = dict?["totalRatings"] as? Int ?? 0
amountOfRatings = dict?["amountOfRatings"] as? Int ?? 1
}){ (error) in
print(error.localizedDescription)
}
return((Int)(totalRatings/amountOfRatings))
}
Database structure
Any tips and help is very appreciated!

You are trying to return value out of completion handler.
Your function should be like:
private func findAverage(byUid uid: String, completion: #escaping (_ average: Int) -> Void) {
// ...
dbRef.observeSingleEvent(of: .value, with: { snapshot in
guard let dict = snapshot.value as? [String: Any] else {
return
}
totalRatings = dict["totalRatings"] as? Int ?? 0
amountOfRatings = dict["amountOfRatings"] as? Int ?? 1
completion((Int)(totalRatings / amountOfRatings))
})
}
Something like this, check Swift docs (about completion handlers etc.).

Try below code:
private func FindAverage(uid: String) -> Int {
var totalRatings = 0
var amountOfRatings = 1
let dbRef = Database.database().reference().child("ratings").child(uid)
dbRef.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
guard let dict = snapshot.value as? [String: Any] else {
return
}
totalRatings = dict?["totalRatings"] as? Int ?? 0
amountOfRatings = dict?["amountOfRatings"] as? Int ?? 1
}
}){ (error) in
print(error.localizedDescription)
}
return((Int)(totalRatings/amountOfRatings))
}

Related

Having Trouble Pulling Data From Firebase RT Database

Super new to coding so apologies if something is super obvious here.
I'm working on an app that I can use to keep track of my weight lifting split. I write the data like this:
public func writeNewExercise(splitName: String, day: Int, exerciseNum: Int, exerciseName: String, sets: String, repsSecs: String, isTimed: Bool, completion: #escaping (Bool) -> Void) {
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference(withPath: "\(user.uid)/splits/\(splitName)/day \(day)/exercise \(exerciseNum)")
var dataDictionary: [String: Any] = [:]
dataDictionary["Exercise Name"] = exerciseName
dataDictionary["Sets"] = sets
dataDictionary["Reps or Secs"] = repsSecs
dataDictionary["Is Timed"] = isTimed
exerciseRef.setValue(dataDictionary) { error, _ in
if error == nil {
completion(true)
return
} else {
completion(false)
return
}
}
}
This gives me a JSON dictionary in Firebase that looks like this:
{
"8aIzPgurRLPPEYDpXWv54r5JjvH3" : {
"splits" : {
"Test Split" : {
"day 1" : {
"exercise 0" : {
"Exercise Name" : "Curls",
"Is Timed" : false,
"Reps or Secs" : "12",
"Sets" : "4"
}
}
}
}
},
What I want to do now is to pull this data so I can insert each exercise into a tableView cell. Don't want to do anything fancy with it -- just be able to view it so I can follow my split. I'm doing this more for practice than practicality. I've tried pulling the data about 15 different ways, and no matter what I do it just won't work. I'm totally stumped. Here is the code I have right now:
public func downloadPost(splitName: String, day: Int, completion: #escaping (Bool) -> Void){
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference()
var exerciseArray = [Exercise]()
exerciseRef.child("Users").child(user.uid).child("splits").child(splitName).child("day \(day)").observe(.value) { snapshot in
if snapshot.exists(){
for x in 0...100{
let nameValue = snapshot.childSnapshot(forPath: "exercise \(x)/Exercise Name").value
let setsValue = snapshot.childSnapshot(forPath: "exercise \(x)/Sets").value
let repsOrSecsValue = snapshot.childSnapshot(forPath: "exercise \(x)//Sets/Reps or Secs").value
let isTimedValue = snapshot.childSnapshot(forPath: "exercise \(x)/Sets/Is Timed").value
let exercise = Exercise(name: "\(nameValue!)",
sets: "\(setsValue!)",
repsOrSecs: "\(repsOrSecsValue!)",
isTimed: isTimedValue as? Bool ?? false)
print(exercise.name)
print(exercise.sets)
print(exercise.repsOrSecs)
print(exercise.isTimed)
exerciseArray.append(exercise)
completion(true)
return
}
} else {
print("no snapshot exists")
}
print(exerciseArray)
}
}
Exercise is a custom class I've created that has a name, amount of sets, amount of reps, and a Bool "isTimed". This code prints:
no snapshot exists, []
Trying other things, I've got it to print something like:
null,
0,
0,
false
Some other stuff I've tried has been:
using slash navigation instead of chaining .childs in the .observe.value
using .getData instead of .observe
throwing DispatchQueue.main.async all over the place
making the exerciseRef be the whole database, then calling to the specific point when assigning the snapshot.value
Much else
I've probably put something like 15 hours into just this at this point, and I really cannot figure it out. Any help would be massively appreciated. I'll watch this post closely and post any info that I may have left out if it's needed.
Thanks!
UPDATE
Got everything working by using the code provided by Medo below. For others trying to do something like this, after pulling the array as Medo demonstrated, just set all the labels in your tableViewCell to ExportedArray[indexPath.row].theClassPropertyYouWant
Here is my solution:
public func downloadPost(splitName: String, day: Int, completion: #escaping (([Exercise]) -> ())){
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
exerciseRef = Database.database().reference()
var exerciseArray = [Exercise]()
exerciseRef.child(user.uid).child("splits").child(splitName).child("day \(day)").observe(.value, with: { snapshot in
guard let exercises = snapshot.children.allObjects as? [DataSnapshot] else {
print("Error: No snapshot")
return
}
for exercise in exercises {
let exerciseData = exercise.value as? [String:Any]
let exerciseName = exerciseData["Exercise Name"] as? String
let isTimed = exerciseData["Is Timed"] as? Bool
let repsOrSecs = exerciseData["Reps or Secs"] as? String
let sets = exerciseData["Sets"] as? String
let exerciseIndex = Exercise(name: "\(exerciseName)",
sets: "\(sets)",
repsOrSecs: "\(repsOrSecs)",
isTimed: isTimed)
exerciseArray.append(exerciseIndex)
}
completion(exerciseArray)
}
}
You can call the function downloadPost and extract the array from it like this:
downloadPost(splitName: "", day: 0, completion: {
aNewArray in
// aNewArray is your extracted array [Exercise]
print("\(aNewArray)")
})
Few things to be aware of:
If you want to ensure that your storing your exercises in order (and extract the data in order) then instead of having exercises 0, 1, 2... (in your database), name it by an id called "childByAutoId". Firebase will auto order them for you as you add/push or extract that data. Replace your writeNewExercise function with:
let user = AuthManager.shared.user
var exerciseRef: DatabaseReference!
let key = Database.database().reference().childByAutoId().key ?? ""
exerciseRef = Database.database().reference(withPath: "\(user.uid)/splits/\(splitName)/day \(day)/\(key)")
var dataDictionary: [String: Any] = [:]
dataDictionary["Exercise Name"] = exerciseName
dataDictionary["Sets"] = sets
dataDictionary["Reps or Secs"] = repsSecs
dataDictionary["Is Timed"] = isTimed
exerciseRef.setValue(dataDictionary) { error, _ in
if error == nil {
completion(true)
return
} else {
completion(false)
return
}
}
Firebase Realtime Database is a breadth first search and download. So you should probably flatten out your database structure as much as possible. This means observing on exerciseRef.child("Users").child(user.uid).child("splits").child(splitName).child("day \(day)") would still download all the exercise days.

How can I get a list of children in a Firebase snapshot in order?

I'm working on an app that records when a user stops a scroll motion, appends the offset of the scroll and an elapsed time to a local array, and then uploads the scroll history to Firebase when the user closes the app.
The data in Firebase is stored with an auto ID at the top. Each scroll offset and elapsed time is then stored within its own auto ID child below the parent. In the Firebase web app, the children are in proper order.
I pull the data from Firebase like so:
ghostref.queryOrderedByKey().queryLimited(toLast: UInt(1)).observe(.childAdded, with: { (snapshot) in
guard let ghostdict = snapshot.value as? [String:[String:String]] else {
print("failure")
return
}
var downloadedghostarray = [(cameray:Float, timeelapse:Double)]()
for key in ghostdict.keys {
downloadedghostarray.append((cameray: Float(ghostdict[key]!["cameray"]!)!, timeelapse: Double(ghostdict[key]!["timeelapse"]!)!))
}
}
While I get the data I need, it is not in the proper order. Is there any way to pull Firebase children in the expected order? Maybe I can order the snapshot's children by key as well?
EDIT: Here is the data as it appears in the Firebase web app in the desired order:
And here is the array that renders using the code above:
By iterating the node fields by key and organizing them by key, you're effectively randomizing the elements in your list. Hash-based dictionaries/maps don't guarantee that order is maintained.
You're going to have to iterate the snapshot using children, which (I believe) ensures that order of the children is maintained. This order should allow you to push them into another array whose order is ensured.
class func downloadAllMessages(forUserID: String, completion: #escaping ([Message]) -> Swift.Void, locationCompletion: #escaping (String) -> Swift.Void) {
if let userID = Helper.shared.driverLoggedInDetails.detail?.userid {
let currentUserID = "D\(userID)"
dbReference.child("users").child(currentUserID).child("conversations_list").child(forUserID).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
let data = snapshot.value as! [String: Any]
let location = data["location"]!
locationCompletion(location as! String)
dbReference.child("messages").child(location as! String).observe(.childAdded, with: { (snap) in
if snap.exists() {
let receivedMessages = snap.value as! [String: Any]
var messages1 = [Message]()
let type = MessageType.text
let text = (receivedMessages as NSDictionary).object(forKey: "text") as? String
let mmText = (receivedMessages as NSDictionary).object(forKey: "mmText") as? String
let messageType = (receivedMessages as NSDictionary).object(forKey: "messageType") as? Int
let fromID = (receivedMessages as NSDictionary).object(forKey: "senderId")as? String
let timestamp = (receivedMessages as NSDictionary).object(forKey: "timeStamp")as? Int
let isRead = (receivedMessages as NSDictionary).object(forKey: "read")as? Bool
let isvisible = UserDefaults.standard.object(forKey: "chatwindow") as? Bool
if fromID != currentUserID, isvisible ?? false {
dbReference.child("messages").child(location as? String ?? "").child(snap.key).child("read").setValue(true)
}
if fromID == currentUserID {
let message = Message.init(type: type, textEn: text ?? "", textMM: mmText ?? "", owner: .receiver, timestamp: timestamp ?? 0, isRead: isRead ?? false, isSuggested: messageType == -1 ? true : false)
messages1.append(message)
} else {
let message = Message.init(type: type, textEn: text ?? "", textMM: mmText ?? "", owner: .sender, timestamp: timestamp ?? 0, isRead: isRead ?? false, isSuggested: messageType == -1 ? true : false)
messages1.append(message)
}
completion(messages1)
}else {
// LoadingIndicator.shared.hide()
completion([])
}
})
// LoadingIndicator.shared.hide()
completion([])
}
}
}
}
U can get by adding a number field in the firebase document from 1..n, so that you can use query based on ascending/ descending. The result will be your expected result

iterate over users in firebase database

I am trying to iterate over the users' information where and save it in an Object.
Here is my data
{
"users" : {
"ApC2wS444YbEEUt5BOpFjkn7YTD3" : {
"UserInfo" : {
"lat" : 123123,
"long" : 0,
"petAge" : 5,
"petNeme" : "zhshs"
}
},
"doRJjseSogNJrCQ55zMGlJwj6jh2" : {
"UserInfo" : {
"UID" : "doRJjseSogNJrCQ55zMGlJwj6jh2",
"lat" : 0,
"long" : 0,
"petAge" : 5,
"petName" : "BBB"
}
}
}
}
My code is like this
ref = Database.database().reference()
tableView.delegate = self
tableView.dataSource = self
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference()
self.ref.child("users").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
let value = snapshot.value as! NSDictionary
let userInfo = value[0] as! NSDictionary
let UID = userInfo["UID"] as! String
let lat = userInfo["lat"] as! Double
let long = userInfo["long"] as! Double
let name = userInfo["petName"] as! String ?? ""
let age = userInfo["petAge"] as! Double
}
}) { (error) in
print(error.localizedDescription)
}
I don't really understand how I should handle snapshots and how to get the data properly
I was able to a fetch a single node but when I try to pass iterate, it brings me back the same node with every iteration
Any help?
In your example you use in a for loop this let userInfo = value[0] as! NSDictionary which later gives you only the first node: [0].
Also let me show you my example of retrieving data from a snapshot (in my case I have cards with autoId):
func loadDataFromDb(completion: #escaping ([Card])->()) {
var cards: [Card] = []
let userRef = getCurrentUserRef()
usersRef.child(userRef).child(Paths.cards).observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let card = Card(snapshot: snapshot) {
cards.append(card)
}
}
completion(cards)
}
}
Also I have failable init in Card:
private enum Constants {
static let name = "name"
static let barcode = "barcode"
}
final class Card {
let name: String
let barcode: String
init(_ name: String, barcode: String) {
self.name = name
self.barcode = barcode
}
init?(snapshot: DataSnapshot) {
guard let value = snapshot.value as? [String : AnyObject],
let name = value[Constants.name] as? String,
let barcode = value[Constants.barcode] as? String else {
return nil
}
self.name = name
self.barcode = barcode
}
}

Contextual type 'Void' cannot be used with dictionary literal

This Error comes up after I relaunched my Project without any changes, never heard of it before.
func toDictionary() -> [String : Any] {
let newPostRef = Database.database().reference().child("posts").childByAutoId()
let newPostKey = newPostRef.key
// 1. save image
if let imageData = image.jpegData(compressionQuality: 0.5) {
let storage = Storage.storage().reference().child("images/\(newPostKey)")
storage.putData(imageData).observe(.success, handler: { (snapshot) in
self.downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
return [ <- The Error appears here!!!
"text" : text,
"imageDownloadURL" : downloadURL,
"numberOfLikes" : numberOfLikes,
"numberOfDislikes" : numberOfDislikes
]
}
)}
}
Maybe the following lines of Code help, as I only read something that this Error occurs because of any false String or something like that...
var text: String = ""
private var image: UIImage!
var downloadURL: String?
var numberOfLikes = 0
var numberOfDislikes = 0
let ref: DatabaseReference!
init(snapshot: DataSnapshot){
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
text = value["text"] as! String
downloadURL = value["imageDownloadURL"] as? String
numberOfLikes = value["numberOfLikes"] as! Int
numberOfDislikes = value["numberOfDislikes"] as! Int
}
}
The issue is that the Firebase function observe is asynchronous, so you cannot use the synchronous return statement to return values from it. You need to declare your function to use a completion handler.
func toDictionary(completion: #escaping ([String:Any])->()) {
let newPostRef = Database.database().reference().child("posts").childByAutoId()
let newPostKey = newPostRef.key
// 1. save image
if let imageData = image.jpegData(compressionQuality: 0.5) {
let storage = Storage.storage().reference().child("images/\(newPostKey)")
storage.putData(imageData).observe(.success, handler: { (snapshot) in
self.downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
let snapShotDict = ["text" : text, "imageDownloadURL" : downloadURL, "numberOfLikes" : numberOfLikes, "numberOfDislikes" : numberOfDislikes ]
completion(snapShotDict)
}
)}
}
Then access it like this:
toDictionary(completion: { dict in
// You can only use `dict` inside the completion handler closure
print(dict)
})

Asynchronous Data in tableView Firebase Swift

I have an issue with my least favourite part in Firebase. I want to pull a post from user's following list (every user has one and only one post). First, I created a completion handler to get a list of all followers from Firebase and store it in userArray array of strings:
func GetUsersInFollowing(completion: #escaping (Bool) -> ()) {
ref.child("following").queryOrdered(byChild: FIRAuth.auth()!.currentUser!.uid).observeSingleEvent(of: .value, with: { (snapshot) in
for group in snapshot.children {
self.userArray.append((group as AnyObject).key)
}
completion(true)
})
}
Now the plan is to pull a post from every element of userArray.
Here is where the problem starts. I call CreatePosts() immediately after GetUsersInFollowing() completes.
func CreatePosts() {
for x in userArray {
var thePost = Post()
print("1")
self.ref.child("users").child(x).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.fullName = value?["fullname"] as? String ?? ""
thePost.username = value?["username"] as? String ?? ""
thePost.profileImageURL = value?["photourl"] as? String ?? ""
print("2")
})
self.ref.child("posts").child(x).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.description = value?["description"] as? String ?? ""
thePost.info = value?["location"] as? String ?? ""
thePost.postImageURL = value?["photoURL"] as? String ?? ""
thePost.timePost = value?["timestamp"] as? NSDate
thePost.upVotes = value?["upvotes"] as? Int ?? 0
})
self.postArray.append(thePost)
self.tableView.reloadData()
}
}
Everything looks ok to me, but it surely isn't. Here's how I create cells:
func configureCell(post: Post) {
self.post = post
self.username.text = post.username
self.profileImage = post.profileImageURL
print("3")
self.fullname.text = post.fullName
self.timestamp.text = post.timePost
self.upvotes.text = post.upVotes
self.location.text = post.location
self.descriptionText.text = post.description
}
The output in the console varies, but usually I get:
1
1
3
3
2
2
The idea is to first retrieve all data from Firebase, add it to post object, append the object to the array and then create cell for that object with data downloaded. Cell is already created even though data is not retrieved. I think that is the problem. Thank you, every suggestion is appreciated.
You need to inner query for combining both user profile data and post data.
Like this -
func CreatePosts() {
//Using userPostArrayObjFetched as a counter to check the number of data fetched.
//Remove this code, if you don't want to wait till all the user data is fetched.
var userPostArrayObjFetched = 0
for (index,userID) in userArray.enumerated() {
print("1" + userID)
self.ref.child("users").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
var thePost = Post()
let value = snapshot.value as? NSDictionary
thePost.fullName = value?["fullname"] as? String ?? ""
thePost.username = value?["username"] as? String ?? ""
thePost.profileImageURL = value?["photourl"] as? String ?? ""
print("2" + userID)
self.ref.child("posts").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.description = value?["description"] as? String ?? ""
thePost.info = value?["location"] as? String ?? ""
thePost.postImageURL = value?["photoURL"] as? String ?? ""
thePost.timePost = value?["timestamp"] as? NSDate
thePost.upVotes = value?["upvotes"] as? Int ?? 0
print("3" + userID)
self.postArray.append(thePost)
// Uncomment if you want to reload data as fetched from Firebase without waiting for all the data to be fetched.
// self.tableView.reloadData()
userPostArrayObjFetched += 1
if userPostArrayObjFetched == userArray.count{
self.tableView.reloadData()
}
})
})
}
}

Resources