IndexOn on firebase Root to query entire database swift 3 - ios

I am trying to query by name inside firebase database, I want to return all the names matching the query. But I can't seem to get it to work, I am getting error Consider adding ".indexOn": "name" at / to your security rules.
Security Rules:
{
"rules": {
".read": "true",
".write": "true",
"goals_new": {
".indexOn": ["name"]
}
}
}
I Can retrieve the names for specific child like this:
let query = ref.child("goals_new").queryOrdered(byChild:"name").queryEqual(toValue: name)
query.observe(.childAdded) { (snapshot) in
// if let values =
if let values = snapshot.value as? [String:String] {
print(values)
print(values["name"]?.count ?? "")
}
}
But I want to retrieve all the names in the database that matches the query
Database Structure:
"goals_new" : [ null, {
"name" : "Eric thomas",
"pic" : “…….”,
"title" : "Be Obsessed with your Goals",
"url" : “……”
},
{
"name" : "Bob Proctor",
"pic" : “……….",
"title" : "Goal Achievement System",
"url" : “………”
},
Any help would be appreciated.

Consider adding ".indexOn": "name" at / to your security rules <- Is only a warning so you don't have to worry much about that.
I'm seeing perhaps an error. Looks like you're using an array to store your objects of 'goals_news' because of the keys are numbers 1, 2, etc... Maybe that's is messing with your query, you have to change them for string keys like every time you add one use firebase method childByAutoID().
Better use .value instead of .childAdded method so you can get all the objects that matches the name, something like this:
let query = ref.child("goals_new").queryOrdered(byChild:"name").queryEqual(toValue: name)
query.observe(.value) { (snapshot) in
guard snapshot.exists() && snapshot.hasChildren() else {return}
for snap in snapshot.children {
var currentSnapValue = (snap as! DataSnapshot).value as! [String: String]
print("Name: \(currentSnapValue["name"])")
}
}
That way you're getting all the objects that matches the name

Related

How to organise data when populating UICollectionView with images using Firebase

I'm new to Swift, and coding in general, and have been working on a project where I'd like to create a UICollectionView populated with images from Firebase.
Each section of the UICollectionView would be a category, and each category would contain images related to that category. Each UICollectionView belongs to a parent, and I need to keep track of which parent has which categories, and which images are in each category.
To track the parents, categories, and images, I've set up the Firebase database in the following way (with bowl being the parent, the names of fruit as categories, and the keys are references to image data stored elsewhere in the database):
"bowl" : {
"apple" : {
"-LOM1R4EH9nszjJp0Va5" : true,
"-LOM1aRZT2XCE-6fvLBK" : true,
"-LOM1hSTmRY6wGrWMvIo" : true,
"-LOM1xnvKE6lc7fizomh" : true
},
"banana" : {
"-LOLmQWLXXyiCUwDBwID" : true
},
"pear" : {
"-LOLHakW-EtqevCeHfzl" : true,
"-LOM2DBGGuX5VQLmBz46" : true
},
"orange" : {
"-LOM26_pm6lbJ1D6hVPB" : true
}
}
The image data section of the database looks as follows:
"image" : {
"fruit" : {
"-LOLHakW-EtqevCeHfzl" : {
"description" : "round orange",
"imageURL" : "https://firebasestorage.googleapis.com/1/image1"
},
"-LOLmQWLXXyiCUwDBwID" : {
"description" : "big banana",
"imageURL" : "https://firebasestorage.googleapis.com/1/image2"
},
"-LOM1R4EH9nszjJp0Va5" : {
"description" : "small apple",
"imageURL" : "https://firebasestorage.googleapis.com/1/image3"
}
}
}
The approach I have been attempting to take is to create a dictionary with the image keys in it, then iterate through the image keys to grab the image data associated with each key (such as the imageURL), and then use the imageURL to download the images and populate the UICollectionView.
I've created a struct, as follows to transform the image data:
struct FruitPicture {
let imageURL: String
let description: String
init(imageURL: String, description: String) {
self.imageURL = imageURL
self.description = description
}
init?(snapshot: DataSnapshot) {
guard
let value = snapshot.value as? [String: AnyObject],
let imageURL = value["imageURL"] as? String,
let description = value["description"] as? String else {
return nil
}
self.imageURL = imageURL
self.description = description
}
func toAnyObject() -> Any {
return [
"imageURL": imageURL,
"description": description
]
}
}
I've been able to gather the imageURLs and populate a UICollectionView but it doesn't include the category details, and so far has involved a lot of manipulation of the data via snapshots, dictionaries, arrays, arrays of dictionaries, and so on, from one configuration to another and back again, and I've now become stuck and confused.
I've started looking at using multiple structs and nesting one within the other, like so, but I'm muddled on it all and am spending hours getting nowhere:
struct Picture {
var url: URL
var image: UIImage?
}
struct PictureCategory {
var name: String
var pictures: [Picture]
}
I was hoping for some advice, or roadmap, or details of how you would approach this, or some sample code, or anything to point me in the right direction. Thanks.
Edit to add more info
Thank you Iraniya your reply was very helpful and helped me consider things in a different way, I really appreciate it.
Taking your advice I've written the following which looks up a bowling creates a snapshot of the image meta data within (e.g the fruit and keys associated with that fruit) then uses those keys to create a snapshot of the image data (e.g key, imageURL, description). I then transform both snapshots into dictionaries, and return the dictionaries to the method which called it:
// GET DATA
static func getPicData(forKey bowlKey: String, completion: #escaping ([String : [Any]], [String : [FruitPicture]]) -> Void) {
var imageMetaDict: [String : [Any]] = [:]
var imageDataDict: [String : [FruitPicture]] = [:]
// DEFINE DATABASE TARGET
let ref = Database.database().reference().child("meta").child("bowl").child(bowlKey).child("fruit")
// GET DATA INTO SNAPSHOT AND TRANSFORM INTO DICTIONARY
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String:[String:Any]] else {
return completion([:],[:])
}
// DEFINE DISPATCH GROUP
let dispatchGroup = DispatchGroup()
// ITERATAE THROUGH DICTIONARY
for (categoryObject, fruitData) in dict {
// CREATE ARRAY TO STORE ITEMS
var itemArray = [String]()
// ITERATE THROUGH ITEMS IN FRUIT DATA
for item in fruitData {
// APPEND ITEM.KEY TO ITEM ARRAY
itemArray.append(item.key)
// ENTER DISPATCH GROUP
dispatchGroup.enter()
// USE ITEM.KEY TO GATHER IMAGE DATA
Service.viewPicData(forKey: item.key) { (fruitItem) in
if let fruitItem = fruitItem {
imageDataDict[item.key] = [fruitItem]
}
// EXIT DISPATCH GROUP
dispatchGroup.leave()
}
}
// STORE ARRAY IN DICTIONARY UNDER FRUIT CATEGORY KEY
imageMetaDict[categoryObject] = itemArray
}
// RETURN COMPLETION
dispatchGroup.notify(queue: .main, execute: {
completion(imageMetaDict, imageDataDict)
})
})
}
Each dictionary looks similar to the following:
imageMetaDict
[
"apple": ["-LOM1R4EH9nszjJp0Va5", "-LOM1xnvKE6lc7fizomh", "-LOM1hSTmRY6wGrWMvIo", "-LOM1aRZT2XCE-6fvLBK"],
"pear": ["-LOLHakW-EtqevCeHfzl", "-LOM2DBGGuX5VQLmBz46"],
"banana": ["-LOLmQWLXXyiCUwDBwID"],
"orange": ["-LOM26_pm6lbJ1D6hVPB"]
]
imageDataDict
[
"-LOM26_pm6lbJ1D6hVPB": [myApp.FruitPicture(imageURL: "https://firebasestorage.googleapis.com/1/image1", description: "pear 1")],
"-LOM2DBGGuX5VQLmBz46": [myApp.FruitPicture(imageURL: "https://firebasestorage.googleapis.com/1/image2", description: "banana 1")],
"-LOLmQWLXXyiCUwDBwID": [myApp.FruitPicture(imageURL: "https://firebasestorage.googleapis.com/1/image3", description: "apple 1")]
]
Is this on the right track with what you were suggesting?
From what I understand the next steps are:
Create an array of fruit.keys sorted alphabetically
Use the fruit.keys to get image.keys from 'imageMetaDict'
Use those image.keys to look up the image data (imageURL, etc) in 'imageDataDict'
Transform all of this data into a new FruitDict which contains fruitCategory -> [fruitObject]
Is this similar to what you were suggesting? I'm happy to hear any further pointers, code or suggestions you have, you've really helped me so far!
To store images Create imageDict hash-map(dictionary) with the key you getting from firebase key in image->>fruits eg: "-LOLHakW-EtqevCeHfzl" with value you are getting or the stuct you already create, now when populating fruits-->apple get key from your bowl array or dict and then use that same key to get the image from imagesDict dict(hashmap you just create earlier
now while storing data in firebase make sure each image has unique keys and store that same key in your bowl-->apple->image that way it will be fast and easy to manage as image data and fruits data are mapped using key you get while storing new image :-) if you like the solution I can explain in more dept :-) #HappyCoding
Example
firebase node
"bowl" : {
"apple" : {
"-LOLHakW-EtqevCeHfzl" : true,
"--LOLmQWLXXyiCUwDBwID" : false,
}
}
"image" : {
"fruit" : {
"-LOLHakW-EtqevCeHfzl" : {
"description" : "round orange",
"imageURL" : "https://firebasestorage.googleapis.com/1/image1"
},
"-LOLmQWLXXyiCUwDBwID" : {
"description" : "big banana",
"imageURL" : "https://firebasestorage.googleapis.com/1/image2"
}
}
}
ImageDict
"-LOLHakW-EtqevCeHfzl":{
"description" : "round orange",
"imageURL" : "https://firebasestorage.googleapis.com/1/image1"
},
"-LOLmQWLXXyiCUwDBwID" : {
"description" : "big banana",
"imageURL" : "https://firebasestorage.googleapis.com/1/image2"
}
or
{"-LOLHakW-EtqevCeHfzl":imageStruct1,
"-LOLmQWLXXyiCUwDBwID" :imageStruct2}
now to show image while populating apple
var keys = boul["apple"].allKeys;
if(boul["apple"][keys[0]]){ //value is true show image
var imageUrl = imageDict[keys[0]["imageURL"]; //if using dict
//or
var image = imageDict[key[0]].imageURL //if using struct
}
Now to store parent, categories and there image details
create a Dict called fruitsDict or whatever with dict in side of another dict making key as fruite name eg: "apple":{apples Details like image price etc} but if you only interested in storing images just create list of images which have true value eg: "apple":[key1, key2...]; (keys you get from imageDict.
Now based on your requirement like
Show all category!! then create all category from fruitDict use that as datasource
and use imageDict and fruitsDict for details
Show only specific category like based on seasonal fruits then crate list of those fruits and show those based on imageDict and fruiteDict
HappyCoding :-)

Firebase queryOrderedByChild() method not giving sorted data

My database structure is some thing like this:
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"score": 4,
},
"ghopper": { ... },
"eclarke": { ... }
}
}
I am trying to retrieve top 20 scores in descending order.
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToLast(20)
queryRef.observeSingleEventOfType(.Value, withBlock: { (querySnapShot) in
print(querySnapShot.value)
})
i am trying to get output like
score": 4
score": 3
score": 2
or
score": 2
score": 3
score": 4
or
2
3
4
Please let me know how to solve this.
When you request the children in a specific order, the resulting snapshot will contain both the data that matches the query and information about the order in which you requested them.
But when you request the .value of the snapshot, the keys+data are converted to a Dictionary<String,AnyObject>. Since a dictionary does not have an extra place to put the information about the order, that information is lost when converting to a dictionary.
The solution is to not convert to a dictionary prematurely and instead loop over the snapshot:
queryRef.observeSingleEventOfType(.Value, withBlock: { (querySnapShot) in
for childSnapshot in querySnapShot.children {
print(childSnapshot.value)
}
})
You can also listen to the .ChildAdded event, instead of .Value, in which case the children will arrive in the correct value:
queryRef.observeSingleEventOfType(.ChildAdded, withBlock: { (childSnapshot) in
print(childSnapshot.value)
})
Update
I just added this JSON to my database:
{
"users" : {
"alovelace" : {
"name" : "Ada Lovelace",
"score" : 4
},
"eclarke" : {
"name" : "Emily Clarke",
"score" : 5
},
"ghopper" : {
"name" : "Grace Hopper",
"score" : 2
}
}
}
And then ran this code:
let queryRef = ref.child("users").queryOrderedByChild("score").queryLimitedToLast(20);
queryRef.observeEventType(.ChildAdded) { (snapshot) in
print(snapshot.key)
}
The output is:
ghopper
alovelace
eclarke
Which is the users in ascending order of score.
Update to add more on getting the scores in descending order
The above code gets the 20 highest scores in ascending order. There is no API call to return themthem in descending score.
But reversing 20 items client side is no performance concern, you just need to write the code for it. See for example this answer.
If you really are stuck on reversing them client side, you can add an inverted score. See this answer for an example of that.
Use method observeEventType instead of observeSingleEventOfType.
Also, make FIRDataEventType to ChildAdded.
Last, If you want Top 20 items, use queryLimitedToFirst instead of queryLimitedToLast.
{
"users" : {
"alovelace" : {
"name" : "Ada Lovelace",
"score" : 4
},
"eclarke" : {
"name" : "Emily Clarke",
"score" : 5
},
"ghopper" : {
"name" : "Grace Hopper",
"score" : 2
}
}
}
For the dataset above
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToFirst(20)
queryRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
print("key: \(snapshot.key), value: \(snapshot.value)")
})
key: ghopper, value: Optional({
name = Grace Hopper;
score = 2;
})
key: alovelace, value: Optional({
name = Ada Lovelace;
score = 4;
})
key: eclarke, value: Optional({
name = Emily Clarke;
score = 5;
})
Snapshot will returns the contents as native types.
Data types returned:
NSDictionary
NSArray
NSNumber (also includes booleans)
NSString
So, you can get your scores this way.
let queryRef = FIRDatabase.database().reference().child("users").queryOrderedByChild("score").queryLimitedToFirst(20)
queryRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
if let scores = snapshot.value as? NSDictionary {
print(scores["score"])
}
})
Optional(2)
Optional(4)
Optional(5)
Moreover, the default of realtime database return everything in ascending order.
If you want descending order, you can make some tricks(4:40) in your database.

Server side filtering of Firebase data

I have a Firebase db structure as follows:
{
"listings" : {
"-KOt8OUGkUphoMyqEXJ2" : {
"created" : 1470911323208,
"ends" : 1470911323209,
"make" : "LONDON TAXIS INT",
"model" : "TX4 BRONZE",
"status" : "For Sale",
},
"-KOt97_P8sJW7woED4aH" : {
"created" : 1470911515115,
"ends" : 1471775515000,
"make" : "NISSAN",
"model" : "QASHQAI N-TEC",
"status" : "For Sale",
},
"-KOt_BYYUEaXu_LNvnUv" : {
"created" : 1470918609414,
"ends" : 1471782609000,
"make" : "MAZDA",
"model" : "3 TS",
"status" : "For Sale",
}
}
}
I use GeoFire to get keys for listings in a given radius. I then use observeSingleEventOfType to return the listing for the key. This all works fine.
However, I would like to only return a listing if the "ends" timestamp > current time. I have tried the following approach:
geoQueryHandle = geoQuery.observeEventType(.KeyEntered, withBlock: {(key, location) in
let listingRef = self.ref.child("listings").child(key)
let now = Int(NSDate().timeIntervalSince1970 * 1000)
let query = listingRef.queryStartingAtValue(now, childKey: "ends")
query.observeSingleEventOfType(.Value, withBlock: {snapshot in
if let listing = Listing(snapshot: snapshot, location: location) {
//...populate my tableview
}
}
})
Can anyone advise me on why my query isn't working?
Thanks
Firebase queries apply ordering/filtering to the properties of the child nodes of the location where you execute them.
queryLocation
child1
filterProperty: filterValue
child2
filterProperty: filterValue
Since your listingRef already points to a specific listing, the order/filter you add will apply to child nodes one level deeper.
The simplest way to get your use-case working seems to simply filter the node client-side:
let listingRef = self.ref.child("listings").child(key)
query.observeSingleEventOfType(.Value, withBlock: {snapshot in
if let listing = Listing(snapshot: snapshot, location: location) {
let now = Int(NSDate().timeIntervalSince1970 * 1000)
// TODO: exit if snapshot.child("ends").getValue() < now
//...populate my tableview
}
}
A more complex, but more efficient, way to accomplish this would be to remove the expired events from the Geofire location.

Firebase - How can I retrieve the objects in a date range?

I have the next JSON structure in Firebase:
{
"-KGX6kYg1NO6d9hn-8um" : {
"phase" : "A",
"timestamp" : "12-18-2015 19:43:37"
},
"-KGXOGSxa3vompZX9UO_" : {
"phase" : "B",
"timestamp" : "03-28-2016 15:28:21"
},
"-KMUvszD-vm3Nu02sofd" : {
"phase" : "A",
"timestamp" : "04-03-2014 03:57:56"
}
}
Is it possible to filter the objects by the timestamp key through a range of date?.. For example, I want to get the objects with timestamp from January 2015 to today date. If not possible, what's the better way to filter the objects by dates?... I'm developing an iOS app.
Thanks.
You can sort the snapshot data by timestamp and define a timestamp limit from which you want your data.
For example you want all data from a specific timestamp timestamp1, your reference handler should look like:
let refHandle = tableRef.queryOrderedByChild("timestamp").queryEndingAtValue("timestamp1").observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
for item in snapshot.children {
}
})
You can also apply number of records that you want by adding queryLimitedToLast or queryLimitedToFirst like :
let refHandle = tableRef.queryOrderedByChild("timestamp").queryEndingAtValue("some_time_stamp").queryLimitedToLast(kPostLimit + 1).observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
for item in snapshot.children {
}
})
Also, you want to have a look at the following post about common sql queries in Firebase.

Firebase in Swift nested query not working properly

I have a JSON structure like the following:
{
"groups" : {
"-KAv867tzVgIghmr15CM" : {
"author" : "ruben",
"name" : "Item A"
},
"-KAv87nqLEG1Jtc04Ebn" : {
"author" : "ruben",
"name" : "Item B"
},
"-KAv88yZe8KTfkjAE7In" : {
"author" : "ruben",
"name" : "Item C"
}
},
"users" : {
"rsenov : {
"avatar" : "guest",
"email" : "ruben#ruben.com",
"groups" : {
"-KAv867tzVgIghmr15CM" : "true",
"-KAv87nqLEG1Jtc04Ebn" : "true",
"-KAv88yZe8KTfkjAE7In" : "true"
}
}
}
}
Every user has the element "groups" with a childByAutoId() key. Then I have the list of all the groups that exists in the app.
Every time that I run the app, I get the current user logged url reference, and I get the list of the groups of that user (in this case, the logged in user is "rsenov" that has 3 groups).
For every group that this user belongs to, I iterate through the groups url reference, looking for getting the information of that 3 groups.
I do this like this:
func loadTable() {
self.groups = []
var counter = 0
self.meses = []
var tempItems = [String]()
DataService.dataService.CURRENT_USER_GROUPS_REF.observeEventType(.Value, withBlock: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
tempItems = []
for snap in snapshots {
DataService.dataService.GROUPS_REF.childByAppendingPath(snap.key).queryOrderedByChild("name").observeEventType(.Value, withBlock: { snapshot in
if let postDictionary = snapshot.value as? Dictionary<String, AnyObject> {
tempItems.append(snapshot.value.objectForKey("name") as! String)
let key = snapshot.key
let group = Group(key: key, dictionary: postDictionary)
self.groups.insert(group, atIndex: 0)
}
counter++
if (counter == snapshots.count) {
self.meses = tempItems
self.miTabla.reloadData()
}
})
}
}
})
}
I think this is not a good idea of iterating in that way. For example, if there is a change of some child in the GROUPS_REF url, the code only runs in that nested code, and since it doesn't have the "snap.key" value got from the for loop, it doesn't work.
Which is the best way to do a good query in this case?
Phew, that took some time to write. Mostly because I don't iOS/Swift a lot:
let ref = Firebase(url: "https://stackoverflow.firebaseio.com/35514497")
let CURRENT_USER_GROUPS_REF = ref.childByAppendingPath("users/rsenov/groups")
let GROUPS_REF = ref.childByAppendingPath("groups")
var counter: UInt = 0
var groupNames = [String]()
CURRENT_USER_GROUPS_REF.observeEventType(.Value, withBlock: { groupKeys in
for groupKey in groupKeys.children {
print("Loading group \(groupKey.key)")
GROUPS_REF.childByAppendingPath(groupKey.key).observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.value)
if (snapshot.childSnapshotForPath("name").exists()) {
groupNames.append(snapshot.value.objectForKey("name") as! String)
}
counter++
if (counter == groupKeys.childrenCount) {
print(groupNames)
}
})
}
})
By the way, this is how you create a minimal, complete verifiable example. The code has no external dependencies (such as Group and DataService in your code) and only contains what's relevant to the answer.
The important bits:
I used observeSingleEventOfType to get each group, since I don't want to get more callbacks if a group changes
I use snapshot.childSnapshotForPath("name").exists() to check if your group has a name. You probably want to either ensure they all have names or add them to the list with some other property in the real app.
Frank's answer is on-point. I wanted to throw in an alternative that may or may not work for your situation as it requires a slight alteration to the database.
groups
gid_0
author: "ruben"
name: "Item A"
users
uid_0: true
gid_1
author: "ruben"
name: "Item B"
users
uid_1: true
gid_2
author: "ruben"
name: "Item C"
users
uid_0: true
And then some ObjC Code for a Deep Query
Firebase *ref = [self.myRootRef childByAppendingPath:#"groups"];
FQuery *query1 = [ref queryOrderedByChild:#"users/uid_0"];
FQuery *query2 = [query1 queryEqualToValue:#"true"];
[query2 observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"key: %# value: %#", snapshot.key, snapshot.value);
}];
This code does a deep query on the /groups for all groups that have a /users/uid_0 = true. In this case it returns gid_0 and gid_2
It eliminates the need for iterations and multiple calls to the database.
Adding a /users/ node to each group with a list of the uid's may offer some additional flexibility.
Just a thought.

Resources