I am writing an iOS app where I will have to observe a node for every contact a user has. E.g. this means observing up to 30 different nodes.
How expensive is this regarding the traffic that is caued by lots of observations instead of one big observation?
EDIT:
Let's assume every registered user adds new messages to:
- messages
- $userId
- $messageId
- timestamp
- text
Now a logged in user wants to retrieve the messages of all of his contacts (much like twitter):
let contactIds = [userId1, userId2, ...] // Array of Strings
for userId in contactIds {
let messagesRef = self.ref.child("messages").child(userId).observe.queryLimitedToLast(100)
messagesRef.observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
// ...
})
}
Since the app shouldn't support followers / following like twitter, this cannot be solved like the firefeed.io example, where every user only observes one single feed node where each one of his contacts writes to.
Related
I have a simple iOS app that part of the app grabs all the users from firebase database so you can search them, and do different functions. Now my question is, if/when the app grows and there are thousands of users, does pulling all the users from the database and adding them to an array of [user]'s, still not crash or slow the app? I see so many people on youtube just loop through firebase and grab all the users. Please note I am excluding profile photos so there is no downloading images involved, just strings. I have some code I thought could solve this possible problem, but I am starting to wonder if there even is a problem with just fetching all the users from firebase and putting them into and array and then displayed in a tableview.
Here is some of my code right now, but it still I notice when I type in one letter, then turn airplane mode on, it downloaded all the users. I really need some help or some advice on this one, thanks.
var checklers = [String]()
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text == "" {
filteredUsers = users
}
else {
print("refreshing")
if let uidi = FIRAuth.auth()?.currentUser?.uid {
view.addSubview(activityInd)
activityInd.startAnimating()
filteredUsers.removeAll()
checklers.removeAll()
let ref = FIRDatabase.database().reference()
ref.child("users").queryOrderedByKey().observe(.value, with: { snapshot in
if let userr = snapshot.value as? [String : AnyObject] {
for (_, velt) in userr {
if let usernamerr = velt["Username"] as? String {
if usernamerr.lowercased().contains(searchController.searchBar.text!.lowercased()) {
let userNew = usera()
if let name = velt["Full Name"] as? String, let uidd = velt["uid"] as? String {
userNew.name = name
userNew.username = usernamerr
userNew.uid = uidd
if self.checklers.contains(uidd) {
print("already")
}
else {
if userNew.uid != uidi {
self.filteredUsers.append(userNew)
self.activityInd.stopAnimating()
self.checklers.append(uidd)
}
print("added a user")
}
}
}
}
self.tableViewSearchUser.reloadData()
}
}
})
ref.removeAllObservers()
}
// filteredUsers = users.filter( { ($0.username?.lowercased().contains(searchController.searchBar.text!.lowercased()))! })
}
tableViewSearchUser.reloadData()
}
Please add any advice, thanks.
Just for searching one or two users, each time a user would need to fetch all the records and putting them all in an array (all in memory). You want SQL-where query function, but Firebase is just different and doesn't have it.
Problem with storing fetching all data approach:
1) Storing just all the user's information in an array of user objects is NOT scalable on client's device.
2) When the number of users gets to ten of thousands, a day worth of search by a single user will eat up a sizable amount of real time database read quota.
3) Stale user data, an user has to re-download all the users just becauase on user changed his name to Doggie1 to doggieTwo.
Solutions:
1) If you haven't done so already, I suggest the options of doing some server-side filtering first by following the best practice here:
Firebase Retrieving Data - Filtering
Downloading a sub-set of user that fits some criteria and then do a bit of client-side filtering. Still is problematic when users get to tens of thousands.
Firebase has a client-size data persistence feature, but in your case if there filtering rule doesn't fit your need, you need do you own caching with some persistent storage solution. Instead of putting the fetched object in an Array[User], I would store each in a database SQLite on iOS and Android apps.
2) Implement a ElasticSearch with the FlashLight plugin, this involves some extra setup (I know, I've been through it, I learned quite a bit), but it is well worth it for autocomplete and search functions that Firebase currently doesn't support.
A pluggable integration with ElasticSearch to provide advanced content searches in Firebase.
I’m using Firebase with the offline ability set to true.
let ref = FIRDatabase.database().referenceWithPath(“my data”).child(“my users id”)
scoresRef.keepSynced(true)
This path also has keep Synced set to true, as with out this with changes in the database are not seen within the app immediately as it is using the local cache.
I have another top level node / path in my app that I want to search - containing other users.
I want to use a singleEvent query and find an email address, I’m doing this via
studios.queryOrderedByChild("email").queryEqualToValue(email).observeSingleEventOfType(.Value, withBlock: { snapshot in // etc
I am able to find the node, however I keep getting the local cached version and not the most recent one in the firebase online store.
If I make some changes to the node online, I don’t get these back within the fetch.
If I changed my fetch to a monitor type i.e.
studios.queryOrderedByChild("email").queryEqualToValue(email).observeEventType(.Value, withBlock: { snapshot in // etc
I get the local cache node first, then get the online updated version as well.
I would rather use the SingleEvent fetch, but I don’t want to monitor the users entire node with keepSynced as it is a high level node and I don’t want to keep all that data locally, as its not directly related to the user.
One fix I found was prior to the single query was add .keepSynced(true) and in the completion block add .keepSynced(false). I'm not sure how much of the node is downloaded this was and may as well use the monitor fetch rather than the singleEvent.
Should I just use the monitorEvent or is there a better way to use SingleEventFetch that goes to the online store and instead of just returning my local node.
PS I am online and this is confirmed via
var connectedRef = FIRDatabase.database().referenceWithPath(".info/connected")
connectedRef.observeEventType(.Value, withBlock: { snapshot in
let connected = snapshot.value as? Bool
if connected != nil && connected! {
println("Connected")
} else {
println("Not connected")
}
})
Thanks
I have a fairly simple scenario whereby I am trying to get the number of users with a particular favorite color. For example, I would like to retreive the number of users that have a favorite color of 'Blue'.
The following code will get me the number of children nodes each user has which in this case is 4 (favoriteColor, displayName, email and provider). I would instead like to get the number of users that have a particular favorite color.
let ref = Firebase(url: "https://project.firebaseio.com/users")
ref.queryOrderedByChild("favoriteColor").queryEqualToValue("Blue").observeEventType(.ChildAdded, withBlock: { snapshot in
print(snapshot.childrenCount)
})
I am trying to keep a live count of the number of users with a particular favorite color via UILabel so I will update the label each time there is a change to the number of results.
Is there currently a way to do this?
I solved this by changing .ChildAdded to .Value.
I also changed observeEventType to observeSingleEventOfType
let ref = Firebase(url: "https://project.firebaseio.com/users")
ref.queryOrderedByChild("favoriteColor").queryEqualToValue("Blue").observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.childrenCount)
})
Hope this helps someone in future!
I am trying to save an object that contains a name string, an address string and location coordinates. In the docs it seems that there is one way to save firebase data and another way to save GeoFire data.
Firebase:
var alanisawesome = ["full_name": "Alan Turing", "date_of_birth": "June 23, 1912"]
var gracehop = ["full_name": "Grace Hopper", "date_of_birth": "December 9, 1906"]
var usersRef = ref.childByAppendingPath("users")
var users = ["alanisawesome": alanisawesome, "gracehop": gracehop]
usersRef.setValue(users)
GeoFire:
let geofireRef = Firebase(url: "https://<your-firebase>.firebaseio.com/")
let geoFire = GeoFire(firebaseRef: geofireRef)
geoFire.setLocation(CLLocation(latitude: 37.7853889, longitude: -122.4056973), forKey: "firebase-hq") { (error) in
if (error != nil) {
println("An error occured: \(error)")
} else {
println("Saved location successfully!")
}
}
Is it possible to save both location and other data in the same request? I'd like to do it in the same request because I don't want the user to be able to create an object without location data if the location request fails. Or is there another smart way to impose that restriction?
Since writing the original entity and its geolocation are two separate calls, they will be two separate write operations. One of these writes is done by your code, the other is done by GeoFire. Both calls write to different parts of the JSON tree.
Firebase recently added the ability to write to multiple locations with a single update() call. With that you could write both the geolocation and the entity in one call. If you want to do that, you'll have to change GeoFire to allow for it.
I also struggled for while finding a solution to this, I ended up taking an unorthodox approach. I make my own key title for the GeoFire key entered. Almost like a vin for a car. The first character is a number defining my annotation to use. Next set of numbers is date generated at time of post, after the date is a string of user data because it doesn't matter how long that text is. And that goes as the key. In my maps app the title key is then called and I retrieve the title as a string and cut it up. There are certain characters that are not allowed to be in the GeoFire key title, so you have to restrict the user from typing those.
I was hoping that someone can help a coding newbie with what might be considered a stupid question. I'm making a blog type app for a community organization and it's pretty basic. It'll have tabs where each tab may be weekly updates, a table view with past updates and a tab with general information.
I setup cloudkit to store strings and pictures, and then created a fetchData method to query cloud kit. In terms of the code (sample below) it works and gets the data/picture. My problem is that it takes almost 5-10 seconds before the text and image update when I run the app. I'm wondering if that's normal, and I should just add an activity overlay for 10 seconds, or is there a way to decrease the time it takes to update.
override func viewDidLoad() {
fetchUpcoming()
}
func fetchUpcoming() {
let container = CKContainer.defaultContainer()
let publicData = container.publicCloudDatabase
let query = CKQuery(recordType: "Upcoming", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil { // There is no error
println(results)
for entry in results {
self.articleTitle.text = entry["Title"] as? String
self.articleBody.text = entry["Description"] as? String
let imageAsset: CKAsset = entry["CoverPhoto"] as! CKAsset
self.articlePicture.image = UIImage(contentsOfFile: imageAsset.fileURL.path!)
self.articleBody.sizeToFit()
self.articleBody.textAlignment = NSTextAlignment.Justified
self.articleTitle.adjustsFontSizeToFitWidth = true
}
}
else {
println(error)
}
}
}
Another question I had is about string content being stored on cloud kit. If I want to add multiple paragraphs to a blood entry (for example), is there a way to put it in one record, or do I have to separate the blog entry content into separate paragraphs? I may be mistaken but it seems like CloudKit records don't recognize line breaks. If you can help answer my questions, I'd be really appreciative.
It looks like you might be issuing a query after creating the data, which isn't necessary. When you save data, as soon as your completion block succeeds (with no errors) then you can be sure the data is stored on the server and you can go ahead and render it to the user.
For example, let's say you're using a CKModifyRecordsOperation to save the data and you assign a block of code to the modifyRecordsCompletionBlock property. As soon as that block runs and no errors are passed in, then you can render your data and images to your user. You have the data (strings, images, etc.) locally because you just sent them to the server, so there's no need to go request them again.
This provides a quicker experience for the user and reduces the amount of network requests and battery you're using on their device.
If you are just issuing normal queries when your app boots up, then that amount of time does seem long but there can be a lot of factors: your local network, the size of the image you're downloading, etc. so it's hard to say without more information.
Regarding the storage of paragraphs of text, you should consider using a CKAsset. Here is a quote from the CKRecord's documentation about string data:
Use strings to store relatively small amounts of text. Although
strings themselves can be any length, you should use an asset to store
large amounts of text.
You'll need to make sure you're properly storing and rendering line break characters between the user input and what you send to CloudKit.