I've found millions of examples and tutorials on how to obtain a FirDatabaseQuery object (or DatabaseQuery as its now been renamed to) using one of the query....() methods.
But not one of these examples then goes on to show what you can do with the query once you have obtained it.
If you use one of the query methods to obtain a subset of the data as a DatabaseQuery, how do you actually access the set of data objects that it represents?
If you can't, for example, iterate through the query results like you can iterate through a snapshot, then what is a DatabaseQuery actually used for then?
I think that the answer to this is easily found in the docs.
I think that in the Android version of the class it is explained pretty well:
The Query class (and its subclass, DatabaseReference) are used for reading data. Listeners are attached, and they will be triggered when the corresponding data changes.
I can explain it in my own words and more practical like this:
You can use the DatabaseQuery object like a DatabaseReference just with the limitation that only the observe functions work and none of the methods used to manipulate the value, i.e. setValue() etc., of the reference nor any methods related to other nodes, i.e. parent and child (child() etc.), exist.
(I took the code from the docs, so please be free to optimize it/bring in your coding style)
This is how you get data from a DatabaseReference:
var postRef: DatabaseReference!
postRef = Database.database().reference().child("posts")
refHandle = postRef.observe(DataEventType.value, with: { (snapshot) in
let postDict = snapshot.value as? [String : AnyObject] ?? [:]
// ...
})
And this is how you get data from a DatabaseQuery:
// My top posts by number of stars
let myTopPostsQuery = (ref.child("user-
posts").child(getUid())).queryOrdered(byChild: "starCount")
refHandle = myTopPostsQuery.observe(DataEventType.value, with: { (snapshot) in
let postDict = snapshot.value as? [String : AnyObject] ?? [:]
// ...
})
As you can see the second part remains the same, as I mentioned, so in retrieving data you handle a DatabaseQuery like a DatabaseQuery. But I have to agree with you on the point that the iOS especially Swift docs are not that well made, the explanations for the other languages are way clearer.
Related
I want to retrieve data on with this value 7hmpcTuCAYQAYRqP7RNmnegSd9r2
But i'm getting all four objects in snapshot. I want to get parent key values which contain this key
7hmpcTuCAYQAYRqP7RNmnegSd9r2
Need to get blue mark keys.
Here is my code
let ref = FirebaseManager.refs.databaseRoot.child("new_ChatListMembers")
ref.queryOrdered(byChild: (Auth.auth().currentUser?.uid)!).queryEqual(toValue: true)
ref.observeSingleEvent(of: .value) { (snapshot) in
print(snapshot)
}
This code return four objects instead of two.Please help me how i can get specific data.
Thanks
You're not actually using the query that you construct in your code. To use that, it'd be something like:
let ref = FirebaseManager.refs.databaseRoot.child("new_ChatListMembers")
let query = ref.queryOrdered(byChild: (Auth.auth().currentUser?.uid)!).queryEqual(toValue: true)
query.observeSingleEvent(of: .value) { (snapshot) in
print(snapshot)
}
But this won't actually scale very well, as you'll need to define an index for each individual UID value in this structure. In short: your current data structure makes it each to find the users for a specific chat room, but it doesn't help finding the chat rooms for a specific user.
To allow the latter, you'll want to add an extra structure in your data:
user_chats: {
"$uid": {
"$chatid": true
}
}
So this is pretty much the inverse of what you have already, which is why this is often called an inverse index.
For more on this, and another example, see my answer here: Firebase query if child of child contains a value
How to loop through Firebase data (childs) which are actually objects and access to their properties in Swift 4?
As a beginner with Swift, I am trying to loop through data I am retrieving from Firebase, and I am trying to access to properties of those objects. Seems much more complicated then it should be in swift (just my subjective opinion)
As per documentation on the Firebase site this is what I have
_commentsRef.observe(.value) { snapshot in
for child in snapshot.children {
// Access to childs here ...
}
}
Now, combining this above and as per tutorials and explanations (btw was not able to find neither one which explains this fully) which I found on net, this is what I have:
ref.child("activities").child("list").observeSingleEvent(of: .value, with: { (snapshot) in
// The list i got here is the list of the childs which are objects
// Lets loop through that list and pull properties we need
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
}
})
The print in the loop will properly display object with all of its properties, but I am not able to access to these properties. Accessing to it with something like "child.value.title" is resulting with error "Value of type 'Any' has no member 'title'"
Do I need to convert child.value to something else, maybe to cast it or to convert it somehow to property accessible JSON or something like that?
If you call value on a snapshot that contains multiple properties, what you get back is a NSDictionary with the property names as the keys. So to get the value of the title key you'd do:
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
let dict = child.value as? [String : AnyObject] ?? [:]
print(dict["title"])
}
Alternatively you can use the other members of DataSnapshot to navigate to the title property and then call .value on that:
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
print(child.childSnapshot(forPath: "title").value)
}
See DataSnapshot.value and the first sample in the Firebase documentation on reading data.
Okay I am reading from a database and when I print the individual variables they print out correctly. However it seems like the data refuses to append to the array. Anyone know why? I can't figure it out at all.
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
//print(snapshot)
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
})
print("helper")
print(commuteArray.count)
return commuteArray
The data is correctly added to the array, just not at the time that you print the array's contents.
If you change the code like this, you can see this:
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
print("added one, now have \(commuteArray.count)")
})
print("returning \(commuteArray.count)")
return commuteArray
You'll see it print something like this:
returning 0
added one, now have 1
added one, now have 2
etc.
This is likely not the output you expected. But it is working as intended. Firebase loads data from its database asynchronously. Instead of blocking your code, it lets the thread continue (so the user can continue using the app) and instead calls back to the code block you passed to observe when new data is available.
This means that by the time this code returns the array it is still empty, but it later adds items as they come in. This means that you cannot return data from a function in the way you are trying.
I find it easiest to change my way of thinking about code. Instead of "First get the data, then print it", I frame it as "Start getting the data. When data comes back, print it".
In the code above, I did this by moving the code that prints the count into the callback block. Instead of doing this, you can also create your own callback, which is called a completion handler or closure in Swift. You can find examples in this article, this article, this question Callback function syntax in Swift or of course in Apple's documentation.
I have set up a Firebase database that has a parent and many different children. I am attempting to save all of the children's keys into an array that I can access elsewhere in the code.
// Used to get the children
rootRef.child(partyName).observe(.childAdded, with: { (snapshot) in
var newItems = [FIRDataSnapshot]()
for item in snapshot.children {
newItems.append(item as! FIRDataSnapshot)
}
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict{
let keyID = each.key
saves.append(keyID)
}
}
})
Unfortunately, most of the "solutions" that I have found online simply print the retrieved data or add it to a Table View. I simply want all the children to be saved in an array that I can access later. Is this possible? If so, how would I do it?
Thanks
The proper way of storing the retrieved data is to store the key-value pairs in an NSDictionary or a Swift Dictionary. As Sachin Vas said, it makes no sense to store just the keys because then you'd have no relation back to the values.
Nevertheless, to answer your question, the code you provided in your question does what you're asking. If saves is a global or static array in some class, it would be accessible globally throughout your application and would contain all the retrieved keys.
The right way of storing the retrieved data would work just the same way, except saves would be a Dictionary.
rootRef.child(partyName).observe(.childAdded, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject] {
saves = snapDict // where saves is declared as Dictionary<String, AnyObject>
}
})
I am new to Firebase and not sure how to best explain this but I will try.
I am trying to have my app create an entry for each user. Then each user entry has multiple (0 through n) sub-entries where each sub-entry is a simple string. Basically there is a user-id (the main entry) and their tasks are the sub-entries.
Now my problem is whenever I push data (the sub-entries) to the main entries, all of the previous sub-entries are deleted and only the most recent one is pushed. I have been looking through the documentation and Googling like crazy but nothing seems to work.
I have tried this:
#IBAction func testWrite(sender: AnyObject) {
let def = NSUserDefaults.standardUserDefaults()
let uid = def.valueForKey("uid")
let root = Firebase(url: getFirebaseURL())
let text = self.tempText.text!
let dataRef = root.childByAppendingPath(uid as! String)
let data = ["test": String(text)]
dataRef.setValue(data)
}
Which appends to the user-id entry fine, with a key of "test" and a value of the 'text'
So then I kill the app and change it to:
#IBAction func testWrite(sender: AnyObject) {
let def = NSUserDefaults.standardUserDefaults()
let uid = def.valueForKey("uid")
let root = Firebase(url: getFirebaseURL())
let text = self.tempText.text!
let dataRef = root.childByAppendingPath(uid as! String)
let data = ["CHANGED": String(text)]
dataRef.setValue(data)
}
And it pushes fine, but then the previous entry was just deleted and the only entry left is this one.
What I am trying to do is maybe incrementally (having a numbered key possibly?) add items one by one without having other entries deleted.
I hope this makes sense :P and any help is greatly appreciated!
What is happening here is, you are setting the entire branch (Users/UserID##), to a value, and that value is a single node Changed:<somestring>
Conceptually, it may help to think of the key you want to set as being just another branch e.g (Users/UserID##/TaskID##)
So conceptually, instead of approaching it like this:
Users/UserID = Key:Value
Approach it like this:
Users/UserID/Key = Value
Note: the branch Users/UserID/Key does not have to exist prior to you assigning it a value.
e.g you could change your reference to point at the subkey you want to add or change:
let dataRef = root.childByAppendingPath(uid as! String + "/Task001")
dataref.setValue(String(text))
I concur that what you are doing is a great way to start learning Firebase, and how it works. But once you get going, instead of generating and using your own key for your list of subtasks, do look into childByAutoId, it will automatically create the subkeys for you, plus much more, and is much easier to manage and code.
Documentation Here
Edit: Suggest referring to Frank's better answer below.
An alternative to #MtlDev's answer would be to use updateChildValues():
let data = ["CHANGED": String(text)]
dataRef.updateChildValues(data)
While setValue() replaces the current data with the new value, updateChildValues() updates it in place.
See the Firebase documentation on updating saved data.