Query an array in Firebase SWIFT - arrays

using swift and firebase I have a list of data like so
{
"posts" : {
"-KHbULJcKqt9bSpxNO9k" : {
"author" : "Michele",
"postText" : "tags as arrays?",
"tag" : [ "HELLO", "WHAT'S UP?", "TEST" ]
}
with many more post. I want to search by tag and if a post contains the search term as a tag return the whole post. Ive been able to do this with strings using queryEqualToValue: but haven't been able to solve it for an array. Any point in the right direction is appreciated thank you.
This is what my save function looks like
func createNewPost(post: Dictionary<String, AnyObject>, tags: [String]) {
let firebaseNewPost = POST_REF.childByAutoId()
let firebaseTag = Firebase(url: "\(POST_REF)/\(firebaseNewPost.key)/tag")
print(firebaseTag)
firebaseNewPost.setValue(post)
for tag in tags {
let tagID = firebaseTag.childByAutoId()
tagID.setValue(tag)
}
}
Now I need help with searching through the data and retrieving certain post based on what a user searches for. This is what I was using when it was just one tag and it was a just a string.
func loadTaggedShit(searchTerm: String) {
DataService.dataService.POST_REF.queryOrderedByChild("tag").queryEqualToValue(seachTerm).observeEventType(.ChildAdded, withBlock: { snapshot in
self.posts = []
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Post(key: key, dictionary: postDictionary)
self.posts.insert(post, atIndex: 0)
}
}
}
self.tableView.reloadData()
})
}
also the data now looks like this
"posts":{
"-KHj_bDJmZJut7knKoUX" : {
"author" : "Michele",
"postText" : "what's up",
"tag" : {
"-KHj_bDJmZJut7knKoUY" : "HEY"
}
}
}

Firebase arrays are very situational and really should be avoided if possible.
Array's in code are very powerful data constructs but they just don't work well within a JSON structure. If they are modified, say, removing a node, that index will be removed as well. That leaves the array with 'holes' e.g. 0, 1, 2, 4, 5, 7 etc. So, create yourself a tags child node within your post node, and then child nodes with keys created with childByAutoId
"posts" : {
"-KHbULJcKqt9bSpxNO9k" : {
"author" : "Michele",
"postText" : "tags as arrays?",
"tags"
tag_id_0
tag_title: "HELLO"
tag_id_1
tag_title: "WHAT'S UP?"
tag_id_2
tag_title: "TEST"
}
You might even consider creating a separate tags node and reference it within the posts node - especially if they are going to be re-usable.
Edit:
Based on some additional information, a different structure will be in order because the OP needs to query for posts that contain a specific tag and the tags are in fact reusable and not specific to a post:
posts
-KHbULJcKqt9bSpxNO9k
author: "Michele"
postText: "tags as arrays?"
tags
tag_id_1: true
tag_id_2: true
-JPHJSojo91920LLK0J
author: "Larry"
postText: "NO, don't use arrays"
tags
tag_id_2: true
tags
tag_id_0
tag_title: "HELLO"
tag_id_1
tag_title: "WHAT'S UP?"
tag_id_2
tag_title: "TEST"
And then the code to receive all posts that use the TEST tag, tag_id_2
This is called a Firebase Deep Query
postsRef.queryOrderedByChild("tags/tag_id_2").queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot)
})

Related

SwiftUI - Using an Array of Ints that is wrapped with #Binding

I gather that the #Binding wrapper is used when a parent view and a child view both need to share a variable without violating the principle of having “a single source of truth”. This isn’t working as I expect when the variable is an Array. Essentially, the data that the child view assigns to this bound variable gets dropped so that the parent view sees an empty array.
The attached example is taken from the Developer Documentation (under XCode’s Help menu) for the #Binding wrapper. Apple’s code shows a simple video controller. It allows a user to play or pause videos (videos are stored as Episode structs). The child view is named PlayButton and toggles between a right-arrow (start) and a double-bar (stop). In Apple’s code the child view had a single variable wrapped with #Binding, which was the isPlaying Boolean variable. When the play button is tapped it toggles the isPlaying variable. As you might expect, the orginal code worked fine.
To demonstrate the problem I’ve modified the child view so that now accepts an array of Episodes. Please assume that the child view must report the size of the show titles to the parent view without violating the single source of truth principle. As a consequence of this need, there is also a new #Binding-wrapped array that records the number-of-characters in a show’s title (for each of the shows).
Additionally, the child view now shows Text Views reporting the show title of each Episode and the title’s size, just to prove that the code is being executed. The parent View should then be able to display the number of elements in its #State array, sizeOfShowTitles. As coded here, I expect the number of episodes to be 1. Instead, the parent view is reporting that sizeOfShowTitles has zero elements.
The playground contains just these 4 elements:
Episode (a struct that identifies the videos)
PlayButton (the child View)
PlayerView (the parent View)
the PlaygroundPage.current.setLiveView(PlayerView()) command, used to excuted SwiftUI in playgrounds.
Can anyone comment on why assignment of values to the bound array is failing?
Note 1:
The problem does not seem to lie with the .append function used in the child view. If you replace the append function with a simple assignment then the outcome is the same - there are no elements in the array in the parent View (tested, but not shown here).
Note 2.
As shown in Apples code (and retained here), assigning a value to a Boolean Type works as expected. So, it appears that the problem has something to do with the Array Type.
Note 3.
I’m using Swift 5.5 in XCode 5.4.1.
// Playground used to show the issue with saving a
// value to an array that is wrapped with #Binding
import SwiftUI
import PlaygroundSupport
// HELPER Struct: records metadata on shows that can be played in PlayerView
struct Episode: Identifiable {
var id = UUID()
var title = "Title"
var showTitle = "Show Title"
}
// CHILD View
// String Array)
struct PlayButton: View {
// input API
#Binding var isPlaying: Bool
var episodes: [ Episode ]
// output API
#Binding var charactersInShowTitle: [ Int ]
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
ForEach( episodes ) { episode in
Text( "CHILD: \( analyzeShowTitle( episode) )" )
}
}
func analyzeShowTitle( _ episode: Episode ) -> String {
let characterCount = episode.showTitle.count
charactersInShowTitle.append( characterCount )
return "\( episode.showTitle ) - \( characterCount ) chars"
}
}
// PARENT
// (modified to show the list of sizes from a String Array)
struct PlayerView: View {
let episodes = [ Episode(title: "Title 1",
showTitle: "Show 1"),
Episode( title: "Title 1",
showTitle: "Show 21")
]
#State private var isPlaying: Bool = false
#State private var sizeOfShowTitles = [ Int ]()
var body: some View {
VStack {
Text("Player App")
.foregroundStyle(isPlaying ? .primary : .secondary)
Text("")
PlayButton(isPlaying: $isPlaying,
episodes: episodes,
charactersInShowTitle: $sizeOfShowTitles )
Text("")
Text( "PARENT no. elements: \( $sizeOfShowTitles.wrappedValue.count)"
)
}
.frame(width: 300)
}
}
PlaygroundPage.current.setLiveView(PlayerView())
How about trying something like this example code,
using .onAppear{...} for updating each episode with the showTitle.count.
Also removing this "appending" from analyzeShowTitle function. This append(...) should not be used within the ForEach loop, because it triggers a view refresh,
which then triggers another append(...) etc...
struct PlayButton: View {
// input API
#Binding var isPlaying: Bool
var episodes: [Episode]
// output API
#Binding var charactersInShowTitle: [Int]
var body: some View {
Button(action: {
isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
ForEach(episodes) { episode in
Text("CHILD: \(analyzeShowTitle(episode))")
}
.onAppear { // <-- here
for episode in episodes {
charactersInShowTitle.append(episode.showTitle.count)
}
}
}
func analyzeShowTitle( _ episode: Episode ) -> String {
return "\( episode.showTitle ) - \( episode.showTitle.count ) chars" // <-- here
}
}

How to update to an array if element already exists

I'm making a React-Native application. Thanks to everyone's help I could somehow make that work except for toggling YES and NO. Once the user clicks on a button I just want to check if that clicked item data already exists in the state, if so I want to update it. If it does not exist then it should be added to the state as a new Item.
I already coded the above logic, my code is working, but not returning correct output, elements are not adding or updating to the state array properly. How do I fix this code?
I want to generate output like this
[{
chelistid:231,
validiary : "YES",
remark : "Hello"
},{
chelistid:232,
validiary : "NO",
remark : "asddddd"
}]
My code
const [reqData, setReqData] = useState([]);
//Modify yes clicked
const yesClicked = (element) => {
let req = {
"chelistid": element.chelistid,
"validiary": "Yes",
"remark": element.remark
}
createCheckList(req);
}
//Modify no clicked
const noClicked = (element) => {
let req = {
"chelistid": element.chelistid,
"validiary": "No",
"remark": element.remark
}
createCheckList(req);
}
const createCheckList = (data) => {
const index = reqData.findIndex(x => x.chelistid === data.chelistid)
var modifiedArray = reqData
if (index !== -1) {
//Remove the element from the array
modifiedArray.splice(index, 1);
}
setReqData([modifiedArray, data]);
}
The problem is it seems like you are not spreading the array to append the data element. What you are doing by [modifiedArray, data] you are creating an array that contains an array and data something like [[modified array content here], data]. But actually, you want to append to modified array instead. For that, you need to expand the modified array by using ... which is called spread syntax. (Learn here) So, your code would look like:
setReqData([...modifiedArray, data]);

Swift: trouble with Arrays of classes

I'm trying to create an array of two items: a URL and a String. The index of the items is of significance.
The data is taken from QuartzFilterManager, which provides information about the Quartz Filters installed on a system.
import Cocoa
import Quartz
class myFilter {
var myURL: URL?
var name: String = ""
}
func getFilters() -> Array<String> {
var filterArray: Array<myFilter>
if let Filters = QuartzFilterManager.filters(inDomains: nil) {
for (index, eachFilter) in Filters.enumerated() {
filterArray[index].name.append((eachFilter as! QuartzFilter).localizedName()!)
filterArray[index].myURL!.append((eachFilter as! QuartzFilter).url()!)
}
}
}
Xcode complains about Type URL not having an append method. But the name property in the preceding line works. In short, how do I set specific properties in my array?
There are lots of issues.
Make myFilter a struct instead of class and name it properly as MyFilter.
You never initialize your filterArray, you only declare it.
You need to create a new instance of MyFilter for each url/name pair you want to add to the array.
You don't need to use enumerated in this case.
You have the wrong return type for your getFilter function (I think).
Use proper naming conventions.
Here's cleaned up code:
struct MyFilter {
var myURL: URL?
var name: String
}
func getFilters() -> [MyFilter] {
var filterArray = [MyFilter]()
if let filters = QuartzFilterManager.filters(inDomains: nil) {
for eachFilter in filters {
let filter = MyFilter(myURL: (eachFilter as! QuartzFilter).url(), name: (eachFilter as! QuartzFilter).localizedName()!)
filterArray.append(filter)
}
}
return filterArray
}
It's still not ideal. Having to cast eachFilter using as! QuartzFilter is clunky.
And other uses of ! are bad. Force-unwrapping the call to localizedName() can crash. Consider proper solutions.
Append is a method of the array struct, not of the URL/String.
You first need to create the array (you just declared it, you actually need to assign something to it)
You then need to create the object that you want to append into the array
You can now append this newly created object to the array
It should look something like this:
import Cocoa
import Quartz
class MyFilter {
var myURL: URL?
var name: String?
init(url: URL?, name: String?) {
self.myURL = url
self.name = name
}
}
func getFilters() -> Array<MyFilter> {
var filterArray = [MyFilter]()
if let filters = QuartzFilterManager.filters(inDomains: nil) {
for filter in filters {
let aFilter = MyFilter(url: filter.url(), name: filter.localizedName())
filterArray.append(aFilter)
}
}
return filterArray
}
Now the array returned by this method will have N MyFilter objects.
You can access every object in the array the way you did before, with
let aFilter = filterArray[index]
And to get the property inside of that object:
let url = aFilter.myURL
let name = aFilter.name
PS: I changed some names to fit the swift conventions (classes are written in PascalCase and variables in camelCase)
PpS: be careful with ! in swift, if it's used on something that happens to be nil will crash the app. Read more about optionals here
PpPs: I was just a few minutes late :D

Accessing final array after fetching from Firebase in Swift

I'm trying to fetch relational data from firebase in swift 3 and storing it in an array. It does fetch everything the way I want it to but i can't access the final array to work with.
I have tried everything I found online but can't make it work properly.
There are 3 child nodes I'm fetching, so every time it fetches it appends it to the array.
The output is:
success
success
success
I just want it to print "success" once.
Here is my code:
// Here the child with the relations is loaded
func fetchFollowingSection1IDs() {
guard let userID = FIRAuth.auth()?.currentUser?.uid else { return }
let reference = FIRDatabase.database().reference().child("interests").child("relations").child("userAndSection1").child(userID)
reference.observe(.childAdded, with: { (snapshot) in
// It's supposed to fetch the details of Section1 according to the childs from the relations (they are the IDs of Section1)
self.fetchSection1(section1ID: snapshot.key, completionHandler: { success in
guard success == true else {
return
}
print("success")
self.collectionView?.reloadData()
})
}, withCancel: nil)
}
// Here it gets the details from Firebase
func fetchSection1(section1ID: String, completionHandler: #escaping (Bool) -> ()) {
let ref = FIRDatabase.database().reference().child("interests").child("details").child("country").child("section1").child(section1ID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.collectionView?.refreshControl?.endRefreshing()
if let dictionary = snapshot.value as? [String: AnyObject] {
let section1 = Section1New(section1ID: section1ID, dictionary: dictionary)
self.section1s.append(section1)
}
completionHandler(true)
}) { (err) in
print("Failed to fetch section1s:", err)
}
}
My Firebase structure for the relations looks like this:
"interests" : {
"relations" : {
"userAndSection1" : {
"7fQvYMAO4yeVbb5gq1kEPTdR3XI3" : { // this is the user ID
"-KjS8r7Pbf6V2f0D1V9r" : true, // these are the IDs for Section1
"-KjS8tQdJbnZ7cXsNPm3" : true,
"-KjS8unhAoqOcfJB2IXh" : true
},
}
Everything loads properly and populates my collection views. It is just the problem that it is the wrong number of Section1s because of the triple appending to the array.
Thank you for your answers!
The code is doing exactly what you are telling it to do.
Your firebase event is .childAdded so it will iterate over each child node one at a time.
It first loads -KjS8r7Pbf6V2f0D1V9r and adds it to the section1s array - there is then one item in the array.
Then it loads -KjS8tQdJbnZ7cXsNPm3 and appends to the array. There's two items in the array and two lines output. etc.
The only thing we don't see in the code in your question is the line that actually prints the array, which is probably in your collectionView delegate methods.
Depending on your use case, you may want to read everything in with .value, and then iterate over that to populate your dataSource array.

OR query in rethink

Is it possible to do OR search on values of array in Rethink.
Eg: I have table called "post" with field "tags" which is an array. I want to do an OR search on values of tags.
post1 {
tags : ["young","agreesive"]
}
post2 {
tags : ["cricket","India"]
}
Give me all posts which contains tags "young" or "cricket" should return be both the posts.
As mlucy said, you can use filter in conjunction with or and contains to get all documents with a specific tag.
If you have an array of tags, you can do the following too:
var tags = ["young", "cricket"];
r.table('posts').filter(function (row) {
return r.expr(tags).contains(function (value) {
return row("tags").contains(value)
});
})
This is more easily extensible than using or.
Multi Index
You can also create a multi-index for that property. Multi-indexes let you lookup data by any of the values in an array. So, for example:
r.table("posts").indexCreate("tags", {multi: true})
r.table("posts").getAll("young", "cricket", {index: "tags"})
This query would get all the documents with the "young" and "cricket" tag, and it's more performant and cleaner than using the nested contains.
You can do that with or and contains:
r.table('posts').filter(function(row) {
return row('tags').contains('young').or(row('tags').contains('cricket'));
})

Resources