Post Data Not Working // Access data outside of closure // Swift Firebase - ios

static func createPost(post: userPost, completion: #escaping (Error?, DatabaseReference?) -> ()) {
// We can assume that a user is already signed in
// initialize the post information
post.setTimestamp()
//post.setLocation()
// Grab the current user's information for associating it with the post
if let user = Auth.auth().currentUser {
post.setUID(uid: user.uid)
let findUserName = Database.database().reference().child("users").child(user.uid)
findUserName.observeSingleEvent(of: .value, with: { (snapshot) in
let dictionary = snapshot.value as? [String: AnyObject]
let theUserName = dictionary?["username"] as? String ?? ""
//print("test:", theUserName)
post.setPostUserName(username: theUserName)
})
}
}
post.setUID(uid: user.uid) <-- function works and will post user id with post.
post.setPostUserName(username: theUserName) <-- not posting username field retrieved from users.
print(theUserName) inside the closure does retrieve username
If I remove post.setPostUserName(username: theUserName) from observeSingleEvent, I tested it with post.setPostUserName(username: "test") and it works. If I write post.setPostUserName(username: theUserName) it does not recognize theUserName
Thank you for your time if you're able to help.

Related

Swift 3 Firebase retrieving key and passing to view controller

I've spend hours looking at identical questions but none of the answers I've found are helping this issue. Simple app retrieves data from Firebase Database and passes to another view controller from the tableview. The main data will pass through but I can't edit the information without an identifying "key" which I tried to set as childByAutoID() but then changed to a timestamp. Regardless of the method, all I get is the entries info not the actual key itself.
func loadData() {
self.itemList.removeAll()
let ref = FIRDatabase.database().reference()
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
if let todoDict = snapshot.value as? [String:AnyObject] {
for (_,todoElement) in todoDict {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
print (snapshot.key);
}
}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
If your data looks like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”
}
}
}
Then I would query like this:
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let child = child as? DataSnapshot
let key = child?.key as? String
if let todoElement = child?.value as? [String: Any] {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
self.tableView.reloadData()
}
}
})
Additionally, like I said in my comment you can just upload the key with the data if you’re using .updateChildValues(). Example:
let key = ref.child("userID!").childByAutoId().key
let feed = ["key": key,
“itemName”: itemName] as [String: Any]
let post = ["\(key)" : feed]
ref.child("userID").child("MyStuff").updateChildValues(post) // might want a completionBlock
Then you can get the key the same way you are getting the rest of the values. So your new data would look like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”,
key: “autoID”
}
}
}
The key you are trying to look for is located in the iterator of your for loop
Inside your if-let, try to do this:
for (key,todoElement) in todoDict {
print(key) // this is your childByAutoId key
}
This should solve the problem. Otherwise show us a screen of your database structure

Firebase - Atomic writes Across Multiple Locations not working

How can I make this function to update the realtime database in Firebase? I managed to make the function work but only to write new ID's for posts when updateChild is triggered. How can I make it to update the current posts by their post ID?
var pathToPictures = [Pictures]()
func updateAllImagesOfCurrentUserInDatabase() {
//refrences
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
//match the user ID value stored into posts with current userID and get all the posts of the user
let update = ref.child("posts").queryOrdered(byChild: "userID").queryEqual(toValue: uid)
update.observe(FIRDataEventType.value, with: { (snapshot) in
// print(snapshot)
self.pathToPictures.removeAll()
if snapshot.key != nil {
let results = snapshot.value as! [String : AnyObject]
for (_, value) in results {
let pathToPostPicture = Pictures()
if let pathToImage = value["pathToImage"] as? String , let postID = value["postID"] as? String {
pathToPostPicture.postImageUrl = pathToImage
pathToPostPicture.postID = postID
self.pathToPictures.append(pathToPostPicture)
print("Image and POST ID: \(pathToPostPicture.postImageUrl!)")
print("Post ID is : \(postID)")
if FIRAuth.auth()?.currentUser?.uid == uid {
ref.child("Users_Details").child(uid!).child("profileImageUrl").observeSingleEvent(of: .value, with: { (userSnapshot) in
print(userSnapshot)
let userIMageUrl = userSnapshot.value as! String
pathToPostPicture.postImageUrl = userIMageUrl
self.pathToPictures.append(pathToPostPicture)
print("This is the image path:" + userIMageUrl + String(self.pathToPictures.count))
// Generate the path
let newPostRef = ref.child("posts").childByAutoId()
let newKey = newPostRef.key as String
print(newKey)
let updatedUserData = ["posts/\(postID)/pathToImage": pathToPostPicture.postImageUrl]
print("This is THE DATA:" , updatedUserData)
ref.updateChildValues(updatedUserData as Any as! [AnyHashable : Any])
})
}
print(self.pathToPictures.count)
}
}
} else {
print("snapshot is nill")
}
self.collectionView?.reloadData()
})
}
UPDATE: This is how my database looks like
This is my database:
"Users_Details" : {
"aR0nRArjWVOhHbBFB8yUfao64z62" : {
"profileImageUrl" : "url",
"userID" : "aR0nRArjWVOhHbBFB8yUfao64z62"
},
"oGxXznrS2DS4ic1ejcSfKB5UlIQ2" : {
"profileImageUrl" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
}
},
"posts" : {
"-KlzNLcofTgqJgfTaGN9" : {
"fullName" : "full name",
"interval" : 1.496785712879506E9,
"normalDate" : "Tue, 06 Jun 2017 22:48",
"pathToImage" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
},
"-KlzNXfvecIwBatXxmGW" : {
"fullName" : "full name",
"interval" : 1.496785761349721E9,
"normalDate" : "Tue, 06 Jun 2017 22:49",
"pathToImage" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
},
Let me tell you what it does: It's looping through the posts and finds all the current user posts. Then it takes the path to image url of every post and assign it to an array. After that it's finding the current user path to image url and updates the whole array with that. Now, I don't know how to update the database with the new values. If anybody knows it will be much appreciate it!
I am looking to make Atomic Writes Across Multiple Locations. Can somebody show me how to fix my code to do that?
It will look something like this:
// ATOMIC UPDATE HERE - IF SOMEBODY CAN FIND THE RIGHT WAY OF DOING THAT
// Generate a new push ID for the new post
let newPostRef = ref.child(byAppendingPath: "posts").childByAutoId()
let newPostKey = newPostRef.key
// Create the data we want to update
let updatedUserData = ["posts/\(newPostKey)": ["pathToImage": self.pathToPictures]] as [String : Any]
// Do a deep-path update
ref.updateChildValues(updatedUserData)
It looks like you're going to be downloading a lot of repetitive data. While denormalization of your database is a good practice, I would argue that in this case, you're better off not including the same download URL in every post. It doesn't make a difference across a handful of posts, but if you have thousands of users and tens or hundreds of thousands of posts, that's a lot of extra data being downloaded. Instead, you could have a dictionary containing uids as keys and the profile imageUrl as the value. You could check the dictionary for the desired uid, and if it's not present, query the database for that user's User_Details, then add them to the dictionary. When you need to display the image, you'd get the url from this dictionary. Someone else may have a better suggestion for this, so I welcome other ideas.
If you'd prefer to keep the profile image in every post, then I recommend using Cloud Functions for Firebase. You can make a function that, when profileImageUrl is updated in the user's User_Details, then updates this entry in the other posts. Currently, Cloud Functions are only available in Node.js. If you don't know JS, please don't let that be a deterrent! It's definitely worth it to learn a bit a JS. And the samples show you about as much JS as you'll have to know to get started.
Check out these resources:
Getting Started with Cloud Functions for Firebase
GitHub samples
Cloud Functions for Firebase documentation
Writing a Database Trigger
Writing a Cloud Storage Trigger: Part 1
Writing a Cloud Storage Trigger: Part 2
After struggling with the logic for a few days I finally got it. If anyone will encounter something like this, this is the answer. In my case it's about updating all the posts of the current user when user changes his image (my posts use denormalisation so every path to image is different). It will loop through the posts, find the current user postsID matching his UserID, place them into an array, finds the currentUser picture and update that array with the path to image. Finally will update the Firebase Database!
var pathToPictures = [Pictures]()
func updateAllImagesOfCurrentUserInDatabase() {
//refrences
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
//match the user ID value stored into posts with current userID and get all the posts of the user
let update = ref.child("posts").queryOrdered(byChild: "userID").queryEqual(toValue: uid)
update.observe(FIRDataEventType.value, with: { (snapshot) in
self.pathToPictures.removeAll()
if snapshot.value as? [String : AnyObject] != nil {
let results = snapshot.value as! [String : AnyObject]
for (_, value) in results {
let pathToPostPicture = Pictures()
if let pathToImage = value["pathToImage"] as? String , let postID = value["postID"] as? String {
pathToPostPicture.postImageUrl = pathToImage
pathToPostPicture.postID = postID
self.pathToPictures.append(pathToPostPicture)
print("Image and POST ID: \(pathToPostPicture.postImageUrl!)")
print("Post ID is : \(postID)")
if FIRAuth.auth()?.currentUser?.uid == uid {
ref.child("Users_Details").child(uid!).child("profileImageUrl").observeSingleEvent(of: .value, with: { (userSnapshot) in
print(userSnapshot)
let userIMageUrl = userSnapshot.value as! String
pathToPostPicture.postImageUrl = userIMageUrl
self.pathToPictures.append(pathToPostPicture)
print("This is the image path:" + userIMageUrl + String(self.pathToPictures.count))
// Update the Database
let postIDCount = pathToPostPicture.postID!
let updatedUserData = ["posts/\(postIDCount)/pathToImage": pathToPostPicture.postImageUrl!]
print("This is THE DATA:" , updatedUserData)
ref.updateChildValues(updatedUserData as Any as! [AnyHashable : Any])
})
}
print(self.pathToPictures.count)
}
}
} else {
print("snapshot is nill - add some data")
}
self.collectionView?.reloadData()
})
}

Retrieving data from Firebase and using it in another Firebase database reference

My data structure is something like the following:
restaurant_owners
|
|owner_id (a unique ID)
|
|restaurant_name
|email
restaurant_menus
|
|restaurant_name
|
|dish_type (drinks, appetizer, etc...)
|
|dish_id (a unique ID)
|
|name
|
|price
The idea of the app is basically to allow "restaurant_owners" to login and manage the menu of their respective restaurant. However I am having problems with the following code: (note that the fetchDish function is called in viewDidLoad)
func fetchDish() {
var restaurantName: String?
let uid = FIRAuth.auth()?.currentUser?.uid
//first time referencing database
FIRDatabase.database().reference().child("owners").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
DispatchQueue.main.async{
restaurantName = dictionary["name"] as? String
print(restaurantName!)
}
}
})
//second time referencing database
FIRDatabase.database().reference().child("restaurants").child(restaurantName!).child("appetizer").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let dish = Dish()
dish.setValuesForKeys(dictionary)
self.dishes.append(dish)
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}, withCancel: nil)
}
What I am trying to do is to retrieve the the name of the restaurant for the current logged in user and store it in the variable "restaurantName". Then when I am referencing the database for the second time I can use this variable inside of .child (e.g.: .child(restaurantName)).
However, when I run this, I get an error saying that the restaurantName (in the database reference) is of value nil. I tried putting in some breakpoints and it seems like the first line of the second database reference is operated before whatever is "within" the first database reference, so basically restaurantName is called before any value is stored in it.
Why is this occurring? How do I work around this problem? Also, what are the best practices to achieve this if I'm doing it completely wrong?
NoSQL is very new to me and I have completely no idea how I should design my data structure. Thanks for the help in advance and please let me know if you need any other information.
UPDATE:
The problem was solved by changing my data structure to what Jay has suggested. The following code is what worked for me: (modified Jay's code a bit)
func fetchOwner() {
let uid = FIRAuth.auth()?.currentUser?.uid
let ownersRef = FIRDatabase.database().reference().child("owners")
ownersRef.child(uid!).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
let restaurantID = dict["restaurantID"] as! String
self.fetchRestaurant(restaurantID: restaurantID)
}
}, withCancel: nil)
}
func fetchRestaurant(restaurantID: String) {
let restaurantsRef = FIRDatabase.database().reference().child("restaurants")
restaurantsRef.child(restaurantID).child("menu").observe(.childAdded, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
let dish = Dish()
dish.setValuesForKeys(dictionary)
self.dishes.append(dish)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
A couple of things:
Firebase is Asynchronous and you have to account for that in your code. As it is in the post, the second Firebase function may execute before the first Firebase function has successfully returned data i.e. restaurantName may be nil when the second call happens.
You should nest your calls (in this use case) to ensure data is valid before working with it. Like this.. and keep reading
let ownersRef = rootRef.child("owners")
let restaurantRef = rootRef.child("restaurants")
func viewDidLoad() {
fetchOwner("owner uid")
}
func fetchOwner(ownerUid: String) {
var restaurantName: String?
let uid = FIRAuth.auth()?.currentUser?.uid
ownserRef.child(ownerUid).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
restaurantId = dict["restaurant_id"] as? String
fetchRestaurant(restaurantId)
}
}
})
}
func fetchRestaurant(restaurantId: String) {
restaurantRef.child(restaurantId).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
let restaurantName = dict["name"] as! String
let menuDict = dict["menu"] as! [String:Any]
self.dataSourceArray.append(menuDict)
menuTableView.reloadData()
}
}
}
Most importantly, it's almost always best practice to disassociate your key names from the data it contains. In this case, you're using the restaurant name as the key. What if the restaurant name changes or is updated? You can't change a key! The only option is to delete it and re-write it.... and... every node in the database that refers to it.
A better options it to leverage childByAutoId and let Firebase name the nodes for you and keep a child that has the relevant data.
restaurants
-Yii9sjs9s9k9ksd
name: "Bobs Big Burger Barn"
owner: -Y88jsjjdooijisad
menu:
-y8u8jh8jajsd
name: "Belly Buster Burger"
type: "burger"
price: "$1M"
-j8u89joskoko
name: "Black and Blue Burger"
type: "burger"
price: "$9.95"
As you can see, I leveraged childByAutoId to create the key for this restaurant, as well as the items on the menu. I also referenced the owner's uid in the owner node.
In this case, If the Belly Buster Burger changes to the Waist Slimming Burger, we can make one change and it's done and anything that references it is also updated. Same thing with the owner, if the owner changes, then just change the owner uid.
If the restaurant name changes to Tony's Taco Tavern, just change the child node and it's done.
Hope that helps!
edit: Answer to a comment:
To get the string (i.e. the 'key' of a key:value pair) immediately created by .childByAutoId()
let testRef = ref.child("test").childByAutoId()
let key = testRef.key
print(key)

Firebase Swift syntax

Can anyone help me convert this from JavaScript to Swift 3 syntax?
I am trying to get all of the clients a specific user has.
I have clients saved in their own node, then I have a list of clientIDs in each of the users.
I believe this code will work, as a similar situation was described in the guide it comes from, data organized in the same way, but it is not swift syntax, particularly .on and .once.
var usersREF =
new Firebase("https://awesome.firebaseio-demo.com/users");
var clientsREF =
new Firebase("https://awesome.firebaseio-demo.com/clients");
var currentUsersClients = userREF.child("userID").child("comments");
currentUsersClients.on("child_added", function(snap) {
clientsREF.child(snap.key()).once("value", function() {
// Render the clients on the link page.
));
});
Here is the data Structure on Firebase:
I suppose other ways to do it might be to grab the current Users clientIDs, then do a call for all clients. Filter them via the clientIDs.
Or
Grab the current users clientIDs, then loop through them, making specific calls to each individual client using the ID.
I just don't know if it is bad practice to make multiple calls like that to firebase within a for loop. Or even worse, to pull down much much more data than is neccesary then filter it.
Thanks for any help!
This will get the clients for uid_0, based on your structure
let userRef = ref.child("users/uid_0")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String:AnyObject]
let clientsDict = userDict["clients"] as! [String:AnyObject]
for client in clientsDict {
let clientId = client.key
print(clientId)
}
})
Use this sample code to get your desired result
func getUsers(forUserID userID: String, completion: #escaping (User) -> Swift.Void, failure: #escaping () -> ()) {
if Auth.auth().currentUser != nil {
Database.database().reference().child("users").child(userID).observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
let receivedMessage = snapshot.value as! [String: Any]
let name = receivedMessage["name"] as? String ?? ""
let id = receivedMessage["id"] as? Double ?? 0.0
let profileurl = receivedMessage["url"] as? String ?? ""
completion(User(name: name, id: id, url: url))
} else {
failure()
}
})
} else {
failure()
}
}

Convert Firebase Dictionary Data to Array (Swift)

This may be a simple answer, so apologies in advance, but I'm stuck because I'm still getting head wrapped around how Firebase works. I want to query a Firebase Database based on unix date data that is saved there and then take the related "Unique ID" data and put it into an array.
The data in Firebase looks like this:
posts
node_0
Unix Date: Int
Unique ID Event Number: Int
node_1
Unix Date: Int
Unique ID Event Number: Int
node_2
Unix Date: Int
Unique ID Event Number: Int
What I have so far is as follows. The query part seems to be working as expected. Where I'm struggling is how to put the "Unique ID Event Number" data into an array. This is the approach that seemed closest to success, which is based on this post, but I get an error that child has no member of "value".
// Log user in
if let user = FIRAuth.auth()?.currentUser {
// values for vars sevenDaysAgo and oneDayAgo set here
...
let uid = user.uid
//Query Database to get the places searched by the user between one and seven days ago.
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("error")
} else {
for child in snapshot.children {
if let uniqueID = child.value["Unique ID Event Number"] as? Int {
arrayOfUserSearchHistoryIDs.append(uniqueID)
}
}
}
})
} else {
print("auth error")
}
Any ideas are greatly appreciated!
Try using this:-
historyRef.queryOrdered(byChild: "Unix Date").queryStarting(atValue: sevenDaysAgo).queryEnding(atValue: oneDayAgo).observeSingleEvent(of: .value, with: { snapshot in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict{
let unID = each.value["Unique ID Event Number"] as! Int
arrayOfUserSearchHistoryIDs.append(unID)
}
}else{
print("SnapDict is null")
}
})
I ended up re-working how I read the Firebase data based on the approach outlined in this post. The actual working code I used follows in case it's helpful for someone else.
// 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)
}
}
})
}

Resources