I'm a little bit confused about addSnapshotListener and getDocuments. As I read in the firebase docs, getDocuments() is retrieving data once and addSnapshotListener is retrieving in real-time.
What I want to ask.
If I'm using getDocuments, and im changing some documents in the Firestore , it will not make the change in the app ? But if im using addSnapshotListener it will ?
I'm making an delivery app, which is the best to use to store pictures of food , descriptions etc.
This is what im using to retrieve labels and pictures from my app :
db.collection("labels").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let newEntry = Labels(
firstLabel: data["firstLabel"] as! String,
secondLabel: data["secondLabel"] as! String,
photoKey: data["photoKey"] as! String
)
self.labels
.append(newEntry)
}
}
DispatchQueue.main.async {
self.tableViewTest.reloadData()
}
getDocuments will return results one time, with the current Firestore data.
addSnapshotListener will return an initial result set (same as getDocuments) and get called any time that data changes.
If your data is modified in Firestore and you've used getDocuments, your app will not be notified of those changes. For example, in your delivery app, perhaps the item goes out-of-stock while the user is using it. Or, the price gets changed, the user is logged in from another device, etc -- many possibilities for why the data might change. By using a snapshot listener, you'd get notified if any of these changes happen.
However, if you're relatively confident you don't need updates to the data (like getting a user's address from the database, for example), you could opt to just use getDocuments.
I've created a Service file in order to handle all of my networking within the Weather Application that I am fine tuning. Within this service file, I use protocols, in order to return the retrieved data from GET requests to the appropriate View Controller.
During my code refactoring, and for the sake of learning, rather than using URLSessions, I decided I wanted to learn how to use Alamofire.
One of my GET requests retrieves an image of either a (sun, cloud, rain cloud, etc.), depending on the weather of a certain city (this is an example of the URL I am submitting my GET request to: http://openweathermap.org/img/wn/03n#2x.png.
Before I imported Alomofire, I would GET the bytes of this image, and render the bytes within UIImage like so:
self.weatherIcon.image = UIImage(data: result)
This worked just fine. But now, when using Alamofire for my request, the challenge I am having is that I'm unable to convert AFDataResponse to type Data, in order to then be rendered to UIImage.
Below you may see my GET Request.
AF.request(myUrl).responseData{response in
debugPrint(reponse)
self.delegate3?.iconServiceDelegateDidFinishWithData(result: response)
}
The response is of type AFDataResponse.
Therefore, when trying to write:
self.weatherIcon.image = UIImage(data: result)
I get an error saying,
Cannot convert value of type 'AFDataResponse (aka 'DataResponse<Data, AFError') to expected argument type 'Data'.
Any help would be much appreciated from the community.
Thanks.
Simple answer:
Create a variable of type Data and assign response.data to this variable.
AF.request(myUrl).responseData{ response in
debugPrint(response)
var imgData : Data //create variable of type data
imgData = Data(response.data!) // access the data through response.data
self.delegate3?.iconServiceDelegateDidFinishWithData(result: imgData)
}
There are many ways to do this. You can access the data directly, as you originally suggested (though I would treat it as an Optional rather than force unwrapping, as it'll crash otherwise).
.responseData { response in
let image = response.data.map(UIImage.init(data:)) // Creates UIImage?
}
You can transform the Result value to maintain any Error you receive.
.responseData { response in
let image = response.result.map(UIImage.init(data:)) // Creates Result<UIImage, AFError>
}
Or you can map the entire DataResponse.
.responseData { response in
let imageResponse = response.map(UIImage.init(data:)) // Creates DataResponse<UIImage, AFError>
}
I have an app that uses a snapshot listener to listen to data in a particular document. However, when a field in the document is updated, the data is read 7-10x over. Never read once, and never read the number of fields that are in my document, it always seems to be an arbitrary number. Also, when the read data prints out, it seems like every printout is the same except for a couple of fields that I'm not setting (like an array prints out "<__NSArrayM 0x282d9f240>" but the number changes on each print). As a result, minimal usage of my app is causing 5-10k reads. I'm trying to reduce the number of reads and I don't know exactly how, but the app has to read as data is updated, but my two questions are:
when I print the data from the listener, does each data print out signify a separate read operation? and
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated, then perform one read instead of multiple reads every time any field is updated? Or another strategy to reduce reads when multiple writes occur?
Not sure if this is helpful, but here is the code I'm using to perform the read...its pretty much the standard code from the firestore sdk:
env.db.collection(env.currentSessionCode!).document(K.FStore.docName).addSnapshotListener { [self] documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching snapshot: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
self.env.data1 = data[K.FStore.data1] as? String ?? "????"
self.env.data2 = data[K.FStore.data2] as? String ?? "????"
self.env.data3 = data[K.FStore.data3] as? [String] ?? ["????"]
self.env.data4 = data[K.FStore.data4] as? [String] ?? ["????"]
self.env.data5 = data[K.FStore.data5] as? Double ?? 0
self.env.data6 = data[K.FStore.data6] as? Double ?? 0
self.env.data7 = data[K.FStore.data7] as! Bool
self.env.data8 = data[K.FStore.data8] as! Bool
print("Current data: \(data)")
Update - For clarification, the way I have been updating my data to firebase is with a environment object, and using "didSet" when the new data is changed/updated in the environment to update it on firebase...I think this might be the root of the problem, as the function called on didSet runs 4-5 times each time it is called...
relevant code:
#Published var data1: String {
didSet {
postValuesToFB(fb: K.FStore.data1, string: data1)
}
}
func postValuesToFB(fb: String, string: String) {
guard let code = currentSessionCode else {
fatalError("Error - Connection Check - no value for current session code in Global Env")
}
let docRef = db.collection(code).document(K.FStore.docName)
docRef.getDocument { document, _ in
guard let document = document else {
return
}
if document.exists {
let session = self.db.collection(code).document(K.FStore.docName)
session.updateData([
fb: string,
K.FStore.dateLastAccessed: FieldValue.serverTimestamp(),
])
return
}
}
}
Based on your comments, it sounds as if you've written no code to remove a listener after it's been added. Based on this, it's relatively safe to assume that your code could be adding many listeners over time, and each one is getting called for each change.
You should take a moment to think about the architecture of your app and figure out when is the appropriate time to remove listeners when they're no longer needed. Usually this corresponds with the lifecycle of whatever component is responsible for display of the data from the query. Review the documentation for getting realtime updates, especially the section on detaching a listener. It's up to you to determine the right time to remove your listener, but you definitely don't want to "leak" a listener as you are now.
A common source of unexpected read charges for developers who are new to Firestore is the Firebase console itself. When that console displays Firestore content, you are charged for those read too. To ensure you measure the impact of your code correctly, test it with the Firebase console closed.
when I print the data from the listener, does each data print out signify a separate read operation?
Not really. You get charged for a document read, when the document is read on your behalf on the server. You are not charted for printing the same DocumentSnapshot multiple times.
is there any way for the listener to be alerted of the update but wait to actually perform the read until the data is updated
Nope. To know the document has changed, the server needs to read it. So that requires a charged read operation.
I know there are some posts about it but I can't find something useful. Therefore, I'm opening new post. I have a device which sends some values over bluetooth. I have function which gets this value like every second. I need to show this data in line chart realtime (Two line). I know Android API has a function for realtime but I can't find it in iOS API.
How I'm doing now is like this;
if (uuid == kUUIDECGSensor) {
let dataDict: [AnyHashable: Any] = LBValueConverter.manageValueElectrocardiography(dataValue)
// print("kUUIDECGSensor dict: \(dataDict)")
let allValues = Array(dataDict.values)
print(allValues)
data_array1.append(allValues[0] as! Double)
data_array2.append(allValues[1] as! Double)
//print(data_array1)
//print(data_array2)
temp_time += 5
time.append("\(temp_time)")
setChart(dataPoints: time, values: data_array1)
}
It is working but It doesnt seem like realtime. What am I missing?
Thank You!
I'm currently working on a iOS app developed in Swift and I need to store some user-created content on the device but I can't seem to find a simple and quick way to store/receive the users content on the device.
Could someone explain how to store and access local storage?
The idea is to store the data when the user executes an action and receive it when the app starts.
The simplest solution for storing a few strings or common types is UserDefaults.
The UserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Boolean values, and URLs.
UserDefaults lets us store objects against a key of our choice, It's a good idea to store these keys somewhere accessible so we can reuse them.
Keys
struct DefaultsKeys {
static let keyOne = "firstStringKey"
static let keyTwo = "secondStringKey"
}
Setting
let defaults = UserDefaults.standard
defaults.set("Some String Value", forKey: DefaultsKeys.keyOne)
defaults.set("Another String Value", forKey: DefaultsKeys.keyTwo)
Getting
let defaults = UserDefaults.standard
if let stringOne = defaults.string(forKey: DefaultsKeys.keyOne) {
print(stringOne) // Some String Value
}
if let stringTwo = defaults.string(forKey: DefaultsKeys.keyTwo) {
print(stringTwo) // Another String Value
}
Swift 2.0
In Swift 2.0 UserDefaults was called NSUserDefaults and the setters and getters were named slightly differently:
Setting
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("Some String Value", forKey: DefaultsKeys.keyOne)
defaults.setObject("Another String Value", forKey: DefaultsKeys.keyTwo)
Getting
let defaults = NSUserDefaults.standardUserDefaults()
if let stringOne = defaults.stringForKey(DefaultsKeys.keyOne) {
print(stringOne) // Some String Value
}
if let stringTwo = defaults.stringForKey(DefaultsKeys.keyTwo) {
print(stringTwo) // Another String Value
}
For anything more serious than minor config you should consider using a more robust persistent store:
CoreData
Realm
SQLite
They Say Use NSUserDefaults
When I was implementing long term (after app close) data storage for the first time, everything I read online pointed me towards NSUserDefaults. However, I wanted to store a dictionary and, although possible, it was proving to be a pain. I spent hours trying to get type-errors to go away.
NSUserDefaults is Also Limited in Function
Further reading revealed how the read/write of NSUserDefaults really forces the app to read/write everything or nothing, all at once, so it isn't efficient. Then I learned that retrieving an array isn't straight forward. I realized that if you're storing more than a few strings or booleans, NSUserDefaults really isn't ideal.
It's also not scalable. If you're learning how to code, learn the scalable way. Only use NSUserDefaults for storing simple strings or booleans related to preferences. Store arrays and other data using Core Data, it's not as hard as they say. Just start small.
Update: Also, if you add Apple Watch support, there's another potential consideration. Your app's NSUserDefaults is now automatically sent to the Watch Extension.
Using Core Data
So I ignored the warnings about Core Data being a more difficult solution and started reading. Within three hours I had it working. I had my table array being saved in Core Data and reloading the data upon opening the app back up! The tutorial code was easy enough to adapt and I was able to have it store both title and detail arrays with only a little extra experimenting.
So for anyone reading this post who's struggling with NSUserDefault type issues or whose need is more than storing strings, consider spending an hour or two playing with core data.
Here's the tutorial I read:
http://www.raywenderlich.com/85578/first-core-data-app-using-swift
If you didn't check "Core Data"
If you didn't check "Core Data"when you created your app, you can add it after and it only takes five minutes:
http://craig24.com/2014/12/how-to-add-core-data-to-an-existing-swift-project-in-xcode/
http://blog.zeityer.com/post/119012600864/adding-core-data-to-an-existing-swift-project
How to Delete from Core Data Lists
Delete Data from Coredata Swift
Okey so thanks to #bploat and the link to http://www.codingexplorer.com/nsuserdefaults-a-swift-introduction/
I've found that the answer is quite simple for some basic string storage.
let defaults = NSUserDefaults.standardUserDefaults()
// Store
defaults.setObject("theGreatestName", forKey: "username")
// Receive
if let name = defaults.stringForKey("username")
{
print(name)
// Will output "theGreatestName"
}
I've summarized it here http://ridewing.se/blog/save-local-data-in-swift/
Using NSCoding and NSKeyedArchiver is another great option for data that's too complex for NSUserDefaults, but for which CoreData would be overkill. It also gives you the opportunity to manage the file structure more explicitly, which is great if you want to use encryption.
For Swift 4.0, this got easier:
let defaults = UserDefaults.standard
//Set
defaults.set(passwordTextField.text, forKey: "Password")
//Get
let myPassword = defaults.string(forKey: "Password")
Swift 5+
None of the answers really cover in detail the default built in local storage capabilities. It can do far more than just strings.
You have the following options straight from the apple documentation for 'getting' data from the defaults.
func object(forKey: String) -> Any?
//Returns the object associated with the specified key.
func url(forKey: String) -> URL?
//Returns the URL associated with the specified key.
func array(forKey: String) -> [Any]?
//Returns the array associated with the specified key.
func dictionary(forKey: String) -> [String : Any]?
//Returns the dictionary object associated with the specified key.
func string(forKey: String) -> String?
//Returns the string associated with the specified key.
func stringArray(forKey: String) -> [String]?
//Returns the array of strings associated with the specified key.
func data(forKey: String) -> Data?
//Returns the data object associated with the specified key.
func bool(forKey: String) -> Bool
//Returns the Boolean value associated with the specified key.
func integer(forKey: String) -> Int
//Returns the integer value associated with the specified key.
func float(forKey: String) -> Float
//Returns the float value associated with the specified key.
func double(forKey: String) -> Double
//Returns the double value associated with the specified key.
func dictionaryRepresentation() -> [String : Any]
//Returns a dictionary that contains a union of all key-value pairs in the domains in the search list.
Here are the options for 'setting'
func set(Any?, forKey: String)
//Sets the value of the specified default key.
func set(Float, forKey: String)
//Sets the value of the specified default key to the specified float value.
func set(Double, forKey: String)
//Sets the value of the specified default key to the double value.
func set(Int, forKey: String)
//Sets the value of the specified default key to the specified integer value.
func set(Bool, forKey: String)
//Sets the value of the specified default key to the specified Boolean value.
func set(URL?, forKey: String)
//Sets the value of the specified default key to the specified URL.
If are storing things like preferences and not a large data set these are perfectly fine options.
Double Example:
Setting:
let defaults = UserDefaults.standard
var someDouble:Double = 0.5
defaults.set(someDouble, forKey: "someDouble")
Getting:
let defaults = UserDefaults.standard
var someDouble:Double = 0.0
someDouble = defaults.double(forKey: "someDouble")
What is interesting about one of the getters is dictionaryRepresentation, this handy getter will take all your data types regardless what they are and put them into a nice dictionary that you can access by it's string name and give the correct corresponding data type when you ask for it back since it's of type 'any'.
You can store your own classes and objects also using the func set(Any?, forKey: String) and func object(forKey: String) -> Any? setter and getter accordingly.
Hope this clarifies more the power of the UserDefaults class for storing local data.
On the note of how much you should store and how often, Hardy_Germany gave a good answer on that on this post, here is a quote from it
As many already mentioned: I'm not aware of any SIZE limitation
(except physical memory) to store data in a .plist (e.g.
UserDefaults). So it's not a question of HOW MUCH.
The real question should be HOW OFTEN you write new / changed
values... And this is related to the battery drain this writes will
cause.
IOS has no chance to avoid a physical write to "disk" if a single
value changed, just to keep data integrity. Regarding UserDefaults
this cause the whole file rewritten to disk.
This powers up the "disk" and keep it powered up for a longer time and
prevent IOS to go to low power state.
Something else to note as mentioned by user Mohammad Reza Farahani from this post is the asynchronous and synchronous nature of userDefaults.
When you set a default value, it’s changed synchronously within your
process, and asynchronously to persistent storage and other processes.
For example if you save and quickly close the program you may notice it does not save the results, this is because it's persisting asynchronously. You might not notice this all the time so if you plan on saving before quitting the program you may want to account for this by giving it some time to finish.
Maybe someone has some nice solutions for this they can share in the comments?
Swift 3.0
Setter :Local Storage
let authtoken = "12345"
// Userdefaults helps to store session data locally
let defaults = UserDefaults.standard
defaults.set(authtoken, forKey: "authtoken")
defaults.synchronize()
Getter:Local Storage
if UserDefaults.standard.string(forKey: "authtoken") != nil {
//perform your task on success }
For Swift 3
UserDefaults.standard.setValue(token, forKey: "user_auth_token")
print("\(UserDefaults.standard.value(forKey: "user_auth_token")!)")
For someone who'd not prefer to handle UserDefaults for some reasons, there's another option - NSKeyedArchiver & NSKeyedUnarchiver. It helps save objects into a file using archiver, and load archived file to original objects.
// To archive object,
let mutableData: NSMutableData = NSMutableData()
let archiver: NSKeyedArchiver = NSKeyedArchiver(forWritingWith: mutableData)
archiver.encode(object, forKey: key)
archiver.finishEncoding()
return mutableData.write(toFile: path, atomically: true)
// To unarchive objects,
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
let object = unarchiver.decodeObject(forKey: key)
}
I've write an simple utility to save/load objects in local storage, used sample codes above. You might want to see this.
https://github.com/DragonCherry/LocalStorage
NsUserDefaults saves only small variable sizes.
If you want to save many objects you can use CoreData as a native solution, or I created a library that helps you save objects as easy as .save() function. It’s based on SQLite.
SundeedQLite
Check it out and tell me your comments
This gives a great explanation for how to do this in Swift 5: https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
Summary:
To set a value:
let defaults = UserDefaults.standard
defaults.set("value", forKey: "key")
To get a String value:
let key = defaults.object(forKey: "StringKey") as? [String] ?? [String]()
To get integer value:
let key = defaults.integer(forKey: "IntegerKey")
I found this answer and it enabled me to save data, but since Swift 4.1 there has been a much easier way to do this using appstorage.
#AppStorage("studentNames") var studentName: String = "Put name here"
Each item must be unique, but using String you can store a large variety of data in here.
I've made a video tutorial to help you do this: http://youtube.com/watch?v=nLsJD6yL9Ps