I think this is simple but can't seem to create the correct query. I'm using Swift with Firebase Realtime Database on iOS.
Consider the following Firebase structure:
-DATA
--UID
---LEVEL1
----HIGHSCORE
I am trying to create a query to find out whether the specific HIGHSCORE number for a level is greater than a local variable. If it is, I want the return to be the higher value from Firebase. If it is the same or lower, I want the return to be null.
I have tried many different queries, here is an example:
ref.child("DATA/UID/LEVEL1/HIGHSCORE").queryStarting(atValue: 5000).observeSingleEvent(of: .value, with:
I know this is fundamentally incorrect, but I think it shows what I am trying to do.
Thanks in advance for any help.
Mike
EDIT: Here is the actual JSON example:
{
"DATA" : {
"USERID" : {
"LEVEL1" : {
"HIGHSCORE" : 5000,
}
}
}
}
The only way I can think of is with an orderByValue:
ref.child("DATA/UID/LEVEL1").orderByValue().queryStarting(atValue: 5000).observeSingleEvent(of: .value, with:
But this only works well if HIGHSCORE is the only child node under LEVEL1. Even then: it doesn't have any bandwidth or performance over the more idiomatic approach:
ref.child("DATA/UID/LEVEL1/HIGHSCORE").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
val score = snapshot.value as! Int
if score > 5000 {
...
}
}
})
Related
So I am building an app where I add certain words to my Firebase Database. When the user enters the word they want to add, I want to check if that word already exists in the database. If it does then I would show an alert. If not, I'll add it to the database. How would I do that with Swift? Any help would be appreciated. Thank you!
Here's the database structure:
speaktext-6-----
wordList
-LWQObIw1PKWJ_B9jNfp
word: "Water"
wordType: "Noun"
You need to take into account there may be a significant number of words so loading all of the words in will not only be slow as they have to loaded and then iterated over to find the word you are looking for, but it may also overwhelm the devices memory.
A simple solution is a Firebase Query - let Firebase do the heavy lifting and just return the node you want. It will be a LOT faster and won't overwhelm the device.
Here's a function that will tell you if a word exists within your firebase structure
func findWord(aWord: String) {
let ref = self.ref.child("wordList")
let query = ref.queryOrdered(byChild: "word").queryEqual(toValue: aWord)
query.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
print("found the word")
//you could expand on this to indicate it was a Noun etc
} else {
print("\(aWord) is not in the wordList")
}
})
}
Also review the Firebase Sorting and Filtering Data Guide
*this assumes a structure of
root
wordList
word_id_0 //created with .childByAutoId
word: "Water"
You just need to make a single event request on the word child, then you can verify if it .exists() and it will return if it's there or not.
let reference = Database.database().reference().child(<#child#>).child(<#Word#>)
reference.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
// handle word existing in database, show alert.
} else {
// handle word not existing in database, make a request to set the word in database.
}
})
According to your comments, you wanna iterate through every element on a child, you can do it by doing this:
let reference = Database.database().reference().child("wordList")
reference.observeSingleEvent(of: .value, with: { (snapshot) in
if let words = snapshot.value as? [String : [String : Any]] {
for word in words {
if let currentWord = word.value["word"] as? String {
if currentWord == "YOUR WORD HERE" {
// handle word in database.
return
}
}
}
// handle word NOT in database.
} else {
// handle empty array.
}
})
What's the best way to load "related" data in swift?
Common setup, if I have a list of users all stored under uid node and contains a list of follows which stores uids, something like:
"users" : {
"abc123" : {
"email" : "test#test.com",
"follows" : {
"xyz789" : true
}
},
"xyz789" : { ... }
}
What's the most efficient way of loading in the data for all the users one user follows? Is it best to loop through each of the uid's with observeSingleEvent(of: .value)?
This is the solution I've come up with, but feels somewhat cumbersome:
func loadRelated(user: User, completion: #escaping (Bool, [UserObject]) -> ()) {
let ref = Database.database().reference(withPath: "users/" + user.uid + "/follows")
ref.observeSingleEvent(of: .value) { snapshot in
var uids = [String]()
for child in snapshot.children {
let userData = child as! DataSnapshot
uids.append(userData.key)
}
let userRef = Database.database().reference(withPath: "users")
var users = [UserObject]()
var count = 0
uids.forEach { uid in
userRef.child(uid).observeSingleEvent(of: .value) { snapshot in
let user: UserObject(from: snapshot)
users.append(user)
count += 1
if count == uids.count {
completion(true, users)
}
}
}
}
}
I don't really want to go down the denormalization path and store each users data under the top level user.
If you are decided on using Realtime Database, it is best practice to create another root node in your case called user-follows. You can create a follow at the path user-follows/$uid/$fid by setting the value to true, then on your app you would have to observeSingleEvent for each snapshot key ($fid) at user-follows/$uid.
To avoid having to observe each follow separately, instead of setting the value to true, you can just store the data you need about a user in user-follows/$uid. However, a user may change their username for example and so you would need to keep the data inside each user-follows up to date. You can utilise Firebase Cloud Functions to maintain the user-follows when a user changes their information.
Otherwise, I would suggest looking at Firebase Firestore, where some nesting is allowed.
If you know that your node at /users will always contain few users, you could try to get all the users at once with a observeSingleEvent(of:) at path /users. Then filter the users with the ones who are in ../follows.
This may pull more data but it might be faster (not sure) and will need less code to handle.
In fact your initial implementation is quite performant already. Just make sure to handle correctly failing of observeSingleEvent(of:) or the condition count == uids.count will never be fulfilled.
By the way storing each user under ../follows will just duplicate your data and will be hard to maintain updated. So yes avoid it.
My data model
{
"channels":{
"channel_key_0":{
"creator":"pqr",
"name":"Channel-1",
"participant":{
"id_1":"Jack",
"id_2":"Harry"
}
},
"channel_key_1":{
"creator":"xyz",
"name":"Channel-2",
"participant":{
"id_3":"Jerry",
"id_2":"Harry"
}
}
}
}
From given above structure I want to fetch channels of only where participant has value "id_1": "Jack".
I am new to Firebase database. Need help to write this query. I tried few things FIRDatabase.database().reference().child("channels").queryOrdered(byChild: "participant").queryEqual(toValue: "Jack", childKey: "id_1"), but giving me null data.
Improvement in structure is also welcome. My idea is to build personal chat by applying this structure.
Try like this way.
let ref = FIRDatabase.database().reference().child("channels")
ref.queryOrdered(byChild: "participant/id_1").queryEqual(toValue: "Jack")
.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot)
})
What I'm trying to do is take the values for stat and sort them in a specific order using the query method and display it in a tableview. The problem is that I'm trying to get the sort value for the query but I cant go any lower than the team level because the value for timestamp is random, the child key values are always static though. I am using a for statement to get the stat value but doing the same for the sort value would involve nesting another observer and doing that in a for statement would probably be bad because it would make a lot of requests. Here is my code for this as well.
func dataObserver() {
ref.child("LeagueDatabase").child(league).child(age).child(team).observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! FIRDataSnapshot
self.playerStatsList.append(snap.value(forKey: "stat") as! String)
self.playerDataTable.reloadData()
}
print(self.playerStatsList)
})
}
JSON
{
"LeagueDatabase": {
"-user created league names-": {
"-user created team names-": {
"-timestamp to seconds-": {
"stat": "-random user content-",
"sort": "-timeSince1970 as double-",
}
}
}
}
}
My DB looks like this:
shows{
show1{
name: //Showname
start: //Timestamp start
end: //Timestamp end
rating: //Showrating INT
}
show2{
...
}
}
How can i query the shows, which are running now (start < now && end > now), ordered by the rating?
Is this even possible with this Database Structure or do i have to change it?
You should name shows' children nodes by their UID, not "show1", "show2", etc. Then you would query your database for the shows ordered by their rating, and use a conditional to test whether each result is within the desired time frame. I haven't actually tested this code, but something like this should work:
ref?.child("shows").child(getUid()).queryOrdered(byChild: "rating").observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children as? [String: AnyObject] {
// filter results
if (child["start"] <= currentTime && child["end"] >> currentTime ) {
// results
resultsArray.append(child)
}
}
However, I recommend reading about denormalizing data in Firebase first:
https://firebase.google.com/docs/database/ios/structure-data
https://stackoverflow.com/a/16651115/3502608
And read the docs over querying after you understand denormalization:
https://firebase.google.com/docs/database/ios/lists-of-data
First of all if you are using timestamps and you want to manipulate them in your front end or perform any algorithmic procedure over the timestamp (i.e > or <) then use NSDate not FIRServerValue.timestamp().
To query your show that are having the end : before the current timestamp try using this:-
let currentTimeStamp = Int(NSDate.timeIntervalSinceReferenceDate*1000)
FIRDatabase.database().reference().child("shows").queryOrdered(byChild: "end").queryStarting(atValue: currentTimeStamp).observeSingleEvent(of: .value, with: {(Snapshot) in
print(Snapshot)
})
This will give you all the shows who are running now. Also for this to work you have to store the value of start and end in similar fashion i.e Int(NSDate.timeIntervalSinceReferenceDate*1000)
To order them according to your show rating , you can only retrieve the values and store them in a struct.
struct show_Struct {
var name : String!
var rating : Int! //If it is int or float if it is of type float.
...
}
Before calling the reloadData() function on any of your tableView or collectionView, just call
let showFeed = [show_Struct]()
..
self.showFeed.sort(by: {$0.rating > $1.rating})
self.tableView.reloadData()