I posted a question previously regarding how to access data inside a child snapshot, where I got a very well-working answer. However, this solution only allowed me to retrieve the data by value - and not my child. You see, I had a JSON tree-structure like this:
Players {
PlayerID {
Username
Rank
Achievements {
Rookie: yes
}
}
And then I realized that I really need multiple 'childByAutoId' under the 'Achievements' - that will look something like this:
Player {
PlayerID {
Username
Rank
Achievements {
autoID1 {
isAchieved: yes
}
autoID2 {
isAchieved: yes
}
}
}
So how would I go on trying to grab the very ChildValues of each childByAutoId? I know for a fact that this works with the 'ChildAdded' on observe Snapshot, however - this doesn't seem to be a feature here.
Here is the code I have right now:
if let childSnapshot = snapshot.childSnapshot(forPath: "Achievements") as? FIRDataSnapshot{
if let achievementDictionary = childSnapshot.value as? [String:AnyObject] , achievementDictionary.count > 0{
if let achieveMedal = achievementDictionary["Rookie"] as? String {
print(achieveMedal)
}
}
}
Help would be greatly appreciated!
How about a JSON tree structure like this:-
Achievements : {
achievement1 : true,
achievement2 : true,
achievement3 : false,
achievement4 : true,
}
For retrieving your achievements:-
if let childSnapshot = snapshot.childSnapshot(forPath: "Achievements") as? FIRDataSnapshot{
if let achievementDictionary = childSnapshot.value as? [String:AnyObject] , achievementDictionary.count > 0{
for each in achievementDictionary{
print(each.0) // achievement name
print(each.1) // isAchieved (true/false)
}
}
}
}
It's not advisable to add such deep nodes in your DB for navigation reason's .
But this code will also work for the childByAutoID case, i.e each.0 will be your autoID AND each.1 will become that id's corresponding dictionary.For deeper details of that dictionary just navigate through the dictionary
Related
I have a social app that I want display to the user the posts that were most recently liked and who were the two most recent people to like that post. Displaying something like "User A and User B liked and 31 others liked your post" where the 31 others liked the post less recently than User A and User B and this specific post was the most recent post of the user's to be liked.
I am trying to figure out the best way to structure firebase given these requirements. I was thinking I would store the lastLiked timestamp associated to the post in likeActivity and then use the timestamp as the key for the users who liked the post (see below).
"likeActivity" : {
"nIhx1SnChjapy4cbrD5sC1WIZXM2" : {
"-M0egnxw7TqEJoEwA0gG" : {
"lastLiked" : 1582337525620,
"userLikes" : {
"1582337525616" : "BmvRlWWuGRWApqFvtT8mXQlDWzz2",
"1582337525620" : "Ff0CxZhQzYVHuqbnsiOKwRAB01D2"
}
},
"-M0jMrisNrnB4usGeCaS" : {
"lastLiked" : 1582337525630,
"userLikes" : {
"1582337525630" : "Ff0CxZhQzYVHuqbnsiOKwRAB01D2"
}
},
"-M0jPBatN45YtfdyTXNM" : {
"lastLiked" : 1582337525616,
"userLikes" : {
"1582337525610" : "bz2E02wmmXQjVkxRF23T2SPkzSf2",
"1582337525616" : "WPOThFRRLDUCeJ8YVmyHFpGgnxI3"
}
}
}
}
Would this be the best way given my requirements from above? If so, I am having a ton of trouble retrieving these for my iOS app because when I call snapshot.children.allObjects I am not just getting the likes but also the lastLiked timestamp. How would I re-work the observeActivity function to pull data ordered by lastLiked and then ordered by timestamp. I read the documentation on ordering data in Firebase but I'm unsure how to apply it in my circumstance.
func observeActivity () {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let newActivity = DataService.ds.REF_LIKE_ACTIVITY.child("\(uid)")
newActivity.observe(.value, with: {(snapshot) in
self.posts = []
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot{
let postKey = snap.key
//self.posts.append(snap.key)
let userLikeData = newActivity.child("\(snap.key)").child("userLikes")
userLikeData.observe(.value, with: {(snapshot) in
if let newSnap = snapshot.children.allObjects as? [DataSnapshot] {
for valueSnap in newSnap {
//Users who have liked the post
print("SNAPTIVITY \(valueSnap.value!)")
let userId = valueSnap.value!
//self.userArray.append("\(userId)")
if self.activityDict["\(postKey)"] != nil {
self.activityDict["\(postKey)"]!.append("\(userId)")
} else {
self.activityDict["\(postKey)"] = ["\(userId)"]
}
}
let postData = DataService.ds.REF_POSTS.child(postKey)
postData.observe(.value, with: { (snapshot) in
if let postDict = snapshot.value as? Dictionary<String, AnyObject> {
let key = snapshot.key
let post = Post(postKey: key, postData: postDict, isLiked: 0)
self.posts.append(post)
}
//self.feedTableView.reloadData()
self.activityTableView.reloadData()
})
}
})
}
}
})
}
In most social network application each user can only like an individual post at most once. Your current data model for these likes is:
"userLikes" : {
"1582337525616" : "BmvRlWWuGRWApqFvtT8mXQlDWzz2",
"1582337525620" : "Ff0CxZhQzYVHuqbnsiOKwRAB01D2"
}
This model allows a single user to like the same post multiple times. And while you can prevent this from happening in your application code, there's no way to enforce it in security rules.
For this reason, a pretty hard rule when modeling data in Firebase&Firestore is: if something must be unique, it must be the key of the child node/document.
So based on that, I'd model this fragment as:
"userLikes" : {
"BmvRlWWuGRWApqFvtT8mXQlDWzz2": 1582337525616,
"Ff0CxZhQzYVHuqbnsiOKwRAB01D2": 1582337525620
}
You'll note that this now also allows you to store the timestamp as a number, which prevents all kind of possible sorting problems down the line.
I assume your data structure is:
likeActivity
$uid
$postId: ...
Since you're retrieving a list of posts for the current user, you can only order the resulting posts on values that are at a fixed path under each post.
So you can sort the posts on their lastLiked, but you can't then determine the order in which the userLikes under that are returned. You'll have to reorder those results in your application code to determine the most recent like(r).
I don't know how to get data from Firebase to my UITableView. All tutorials I have been watching used Firebase Authentication, in their videos all worked out fine but while trying to replicate it, I failed.
Here is what I tried:
First there is my Database struct:
And here is my code:
func observePosts(){
let postsRef = Database.database().reference().child("posts")
postsRef.observe(.value, with: { snapshot in
var tempPosts = [Post]()
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let dict = childSnapshot.value as? [String:Any],
let text = dict["text":""] as? String
{
let post = Post(text: text)
tempPosts.append(post)
}
}
self.posts = tempPosts
self.tableView.reloadData()
})
}
So my question is: How do I get for example the message from a database structure like this?
let text = dict["text":""] as? String {
let post = Post(text: text)
tempPosts.append(post) }
This is wrong. There is nothing like dict["text":""] . First you dont have any data for text keyword , Second you dont write this :""] , you can call as dict["text"].
I think you solution is let text = dict["title"] as? String . But you architecture is wrong. You can declare just one keyword . title 1 , title 2 is wrong. You have to set just title.
If you have followed YouTube tutorials, there's a high chance you did not modify your firebase rules to reflect on non-authenticated users. If you didn't, Firebase ignores every read/write request. To enable this, Edit your firebase rules by:
Set the rules to TRUE
{
"rules": {
".read": true,
".write": true
}
}
However, there's more to it. Don't do it this way unless you're in test mode. Google has some nice docs about it:
Visit https://firebase.google.com/docs/database/security to learn more about security rules.
Firebase Structure
Hi, I am try to work out how to query jobBrand & jobName in my Firebase Database (Structure Attached) and store it in a tableView. I am going to store more information under each Job Brand so I would like to keep the structure this way if possible?
So far, I can get tableView to read the 2 fields if they are one level up, so the structure would be:
tbaShootApp -> Jobs -> Job Brand > data.
I cannot work out to query down another level and store it in the tableView. Is this possible?
I am using a dictionary to store the job information:
class Job: NSObject {
var id: String?
var jobBrand: String?
var jobName : String?
var director : String?
var jobInfo : jobInfo?
init(dictionary: [String: AnyObject]) {
self.id = dictionary["id"] as? String
self.jobBrand = dictionary["jobBrand"] as? String
self.jobName = dictionary["jobName"] as? String
self.director = dictionary["Director"] as? String
}
}
Here is the code to query the data - I have the function 'fetchJobs' in my superViewDidLoad.
func fetchJobs() {
Database.database().reference().child("Jobs").observe(.childAdded) { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let job = Job(dictionary: dictionary)
job.id = snapshot.key
self.jobs.append(job)
//this will crash because of background thread, use dispatch_async to fix
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}
}
JSON
{
"Jobs" : {
"Candycrush" : {
"cameraInfo" : {
"cameraBody" : "A",
"cameraBrand" : "Arri",
"cameraType" : "Alexa"
},
"jobInfo" : {
"jobBrand" : "CandyCrush",
"jobName" : "Winter"
}
},
"Honda" : {
"cameraInfo" : {
"cameraBody" : "A",
"cameraBrand" : "Arri",
"cameraType" : "Alexa XT"
},
"jobBrand" : "Honda",
"jobName" : "Comet"
},
"WWF" : {
"cameraInfo" : {
"cameraBody" : "A",
"cameraBrand" : "Red",
"cameraType" : "Epic"
},
"jobInfo" : {
"jobBrand" : "WWF",
"jobName" : "Eye"
}
}
}
}
There's a long way to go but maybe this will help:
.child("Jobs").observe(.childAdded)
It will let you know each time a new Jobs is added (Candycrush, Honda etc).
(Note - apart from anything else, you very likely also want to observe removals on that list, also.)
If you are making a table (or whatever ... some sort of list, paging thing, collection of tabs, or the like):
almost certainly each row of the table will, on it's own, want to observe that job.
So the first row, would observe for changes in Jobs/Candycrush
So the second row, would observe for changes in Jobs/Honda
And so on. Each table row (or screen, panel, bubble, tab, or whatever it is) would, on it's own, observe that thing.
Incidentally, almost certainly the "first level" header there (where you have Honda, Candycrush etc) would be an id string. (Just use a UUID, or let Firebase do it.) And then a field "Title" would be Honda etc. It would be very unusual to use the actual title as the sort of ID there. (Note that, apart from anything else, you then can't change/edit the title!).
UPDATE
Since you have added a lot more information since I posted my answer. It is now obvious that you want to query deeper. Additionally, since you posted the actual JSON it is now obvious that the JobBrand node is dynamic. You can query one level below an unknown child. You can read about that here.
I'm going to change .observe(.childAdded) to .observeSingleEvent(of: .value) because you are querying so deep I doubt you want to observe that node, but I could be wrong. Changing this will pull the data in once and only once. If you want to update that value you will have to query again.
You want a query like this:
Database.database().reference().child("Jobs").queryOrdered(byChild: "jobInfo/jobBrand").queryEqual(toValue: "JobBrandYouWant").observeSingleEvent(of: .value, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
let job = Job(dictionary: dictionary)
job.id = snapshot.key
self.jobs.append(job)
}
})
ORIGINAL ANSWER
You current query is going to return all of the data under jobs. Which includes the nodes Job Brand -> jobinfo -> etc.
If you are trying to get less data then you need to know either the JobBrand, jobinfo or both. However, I think you want all of the jobs and your query just isn't working.
You current query fails because your dictionary contains everything under "Jobs" not just one job. You want to loop over the data snapshot and
get each job before you call init.
Database.database().reference().child("Jobs").observe(.childAdded, with: { snapshot in
for child in snapshot.children { // over over the snapshot and cast each "job" to it's own dictionary
let child = child as? DataSnapshot
if let key = child?.key {
if let dictionary = child?.value as? [String: AnyObject] {
let job = Job(dictionary: dictionary)
job.id = key
self.jobs.append(job)
}
}
}
})
The documentation provides this example for looping over a snapshot
_commentsRef.observe(.value) { snapshot in
for child in snapshot.children {
...
}
}
I want to find the youngest user in my list of users and load their data: name, profile pict, and current job assignments. I have read the Firebase primer on querying data, but their examples don't work for me because my data is organized differently. I have an additional child layer.
This is my JSON tree in Firebase:
I've tried loading the list of users and then iterating over them to find the youngest user, but that seems like overkill. The Firebase documentation makes me think I should be able to do the query through a Firebase method, like 'queryOrderedByChild' or similar.
I've gone over the old documentation here and the new documentation here, but I'm still left wondering what to do.
So this is my workflow:
The app will find the youngest user in the list of "members" and load their name, profile pict, birthday, etc. They will choose from a list of jobs. Once that user has chosen from the lists of available jobs, the app will load the next youngest user from the list of "members", and so on until all users have been loaded and have been given the chance to select jobs.
I think a better workflow would be this:
Get youngest user by utilizing a Firebase query
Use that query to load that user (image and name)
How would I go about doing that?
EDIT #1: Code I've Tried
func loadExistingUsers(completion: #escaping ([[String : Any]]) -> ()) {
var dictionary = [[String : Any]]()
ref.child("members").observeSingleEvent(of: .value) { (snapshot: FIRDataSnapshot) in
for child in snapshot.children {
let snap = child as! FIRDataSnapshot
if let value = snap.value as? [String : Any] {
dictionary.append(value)
}
}
completion(dictionary)
}
}
And then in ViewDidLoad:
loadExistingUsers { (dictionary) in
var youngestBirthday = 19000101
var userName = "Sophie"
for item in dictionary {
let fetchedBirthday = item["birthday"] as! Int
let fetchedName = item["firstName"] as! String
if fetchedBirthday > youngestBirthday {
youngestBirthday = fetchedBirthday
userName = fetchedName
}
}
print(userName,youngestBirthday)
}
This method returns the youngest user from my list of users, but it seems like an awfully long way to go to get what I want. I have to first fetch the users from Firebase, and then parse the snapshot, then create an array, then sort the array, then get the user name. I was under the impression Firebase could do all that with one query. Am I wrong?
You can get the youngest child using this code: (since your youngest date is the largest number so I am using toLast)
ref.child("members").queryOrdered(byChild:"birthday").queryLimited(toLast: 1).observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
let name = value["firstname"] as? String
//you can do for other values as well
print(name)
}
})
i was trying to write a code to pull a data from the realtime database using observe for child changed. But i don't know what the mistake is but the code fails. From my debugging study, i found that the observe was successfully triggered when the value changed, but it fails when i try to get the snapshot value to a variable. How should data be retrieved in a observe for child change case.
func userBalance() {
DatabaseProvider.Instance.userRef.child(UserDataHandler.Instance.user_id).observe(FIRDataEventType.childChanged, with: { (snapshot) in
print("Snapshot: ", snapshot)
print("Snapshot Value", snapshot.value)
guard let data = snapshot.value as? NSDictionary else {
print("checkpoint fail test")
return
}
guard let userBalance = data[Constants.BALANCE] as? String else { return }
// update UserDefaults
userDefault.setString(userBalance, forKey: "userBalance")
//update local session
UserDataHandler.Instance.balance = userBalance
}) }
kindly help me out, thanks in advance.
Debugging Data :
Snapshot: Snap (balance) 100
Snapshot Value Optional(100)
checkpoint fail test
Thanks guys, i found the answer, may be it will help someone else. I am put down the new code that worked.
DatabaseProvider.Instance.userRef.child(UserDataHandler.Instance.user_id).observe(FIRDataEventType.childChanged, with: { (snapshot) in
guard let key = snapshot.key as? String else { return }
guard let value = snapshot.value as? String else { return }
if key == Constants.BALANCE {
guard let userBalance = value as? String else { return }
// update UserDefaults
userDefault.setString(userBalance, forKey: "userBalance")
//update local session
UserDataHandler.Instance.balance = userBalance
}
}) }
The problem was that the observe detects the change in the database as one by one, so the snapshot comes as a single data for every change, if you change multiple values in a go, the observe detects it as multiple changes, one by one with each change as change of one value. So when i changed it to direct string it worked. You were right #Achref Gassoumi the problem was with the cast. Just after you told about it, i tried it out. Thanks.
I think your problem probably is with the cast
try the following :
DatabaseProvider.Instance.userRef.child(UserDataHandler.Instance.user_id).observe(FIRDataEventType.childChanged, with: { (snapshot) in
guard let data = snapshot.value as? [<String,anyObject>] else {
print("checkpoint fail test")
return
}
print (data)