I'm using Firebase and iOS to keep a time series of an integer value over the last 24 hours. I have tried using both the .updateChildValues() and .setValue() from the docs, but haven't yet figured out how to keep firebase from overwriting every child value, not just the child value with the same key.
func writeStepsPost(withUserID userID: String, steps: NSNumber) {
let timeStamp = NSDate().timeIntervalSince1970 as NSNumber
let post_user: NSString = userID as NSString
let value: NSNumber = steps
let post = ["uid": post_user,
"steps": value,
"lastUpdate":timeStamp]
let childUpdates = ["/posts-steps-user/\(userID)/": post]
ref.updateChildValues(childUpdates)
let currentHour = Calendar.current.component(.hour, from: Date())
let hourlyPost = ["steps":value]
let dailyUpdates = ["/posts-steps-user/\(userID)/pastDay/\(currentHour):00/": hourlyPost]
print("posting hourly steps update")
ref.updateChildValues(dailyUpdates)
When the time changes from 10 to 11, the node for '10:00':123 is replaced by '11:00':243, when I need to add a node for 11 while leaving 10 in place until the next day. I suspect that since the function is pushing two updates, the first update replaces the existing node.
Any ideas?
Given a single key path like /posts-steps-user/\(userID)/, updateChildValues only updates data at the first child level, and any data passed in beyond the first child level is a treated as a setValue operation. Multi-path behavior allows longer paths to be used without overwriting data. It is documented very well in this Firebase Documentation.
I've tested with the following adjustment to your code where I define multiple paths for your first updateChildValues so it won't overwrite your pastDay and it is working properly.
let childUpdatePath = "/posts-steps-user/\(userID)/"
ref.updateChildValues([
"\(childUpdatePath)/uid": post_user,
"\(childUpdatePath)/steps": value,
"\(childUpdatePath)/lastUpdate": timeStamp
])
let currentHour = Calendar.current.component(.hour, from: Date())
let hourlyPost = ["steps":value]
let dailyUpdates = ["/posts-steps-user/\(userID)/pastDay/\(currentHour):00/": hourlyPost]
ref.updateChildValues(dailyUpdates)
Related
I have some code I want to run after a particular date/time has passed. For example, if I want the code to run 7 days from now and the user opens the app at any time on day 7 or after the code will run but if they open the app before the beginning of day 7 nothing happens. Timers in the main runloop work but only if the app is still running in the background. I need a method that will work even if the user kills the app.
Your best option is to store it as local data Even though you only want the code to run once, the overhead is so low, the "check" will not impact the speed or feel of the application. Also this will allow you to run additional checks .. If someone deletes the app, for instance, and leaves the local storage behind. If they re-install you could theoretically "remember" that the application has been installed, and said code has already run (until the user clears application data)
Something like:
//Globally set key
struct defaultsKeys {
static let keyDate = "dateKey"
}
// Set the date in local storage
let defaults = UserDefaults.standard
defaults.set("Your Date String", forKey: defaultsKeys.dateKey)
// Get the date from local storage
let defaults = UserDefaults.standard
if let stringDate = defaults.string(forKey: defaultsKeys.dateKey) {
print(stringDate)
// Do your date comparison here
}
Very few lines of code, and even though the check happens every time the application starts .. The overhead is negligible.
You can either set the date you want your app to "remember" on your local storage or web service. Then, when the user opens your app, compare that date to current device time to determine if you should execute your code.
First, save the current time when you want. You can set the key name however you want.
UserDefaults.standard.setValue(Date(), forKey: "rememberTime")
And every time I open the app, You compare the current time with the saved time.
To do so, I created a function that compares time.
extension Date {
func timeAgoSince() -> Bool {
let calendar = Calendar.current
let unitFlags: NSCalendar.Unit = [.day]
let components = (calendar as NSCalendar).components(unitFlags, from: self, to: Date(), options: [])
if let day = components.day, day >= 7 {
// Returns true if more than 7 days have passed.
return true
}
return false
}
}
Recall the previously saved time and use the time comparison function.
let beforeTime: Date = (UserDefaults.standard.object(forKey: "rememberTime") as? Date)!
if beforeTime.timeAgoSince() {
// more than seven days later
...
} else {
...
}
If you have a problem, please leave a comment !
You can use the below sample code:
override func viewDidLoad() {
super.viewDidLoad()
let nextCodeRunDate = Date() + (7 * 24 * 60 * 60) // 7 Days
if let savedDate = UserDefaults.standard.value(forKey: "NEXT_DATE") as? Date {
if Date() > savedDate {
UserDefaults.standard.setValue(nextCodeRunDate, forKey: "NEXT_DATE")
runYourCode()
}
}else {
// First time
UserDefaults.standard.setValue(nextCodeRunDate, forKey: "NEXT_DATE")
runYourCode()
}
}
func runYourCode() {
// Your code
}
I'm completely lost in finding a tutorial or some sort of answer.
I'm trying to add a number (+1 for example), to a variable (that gets saved locally), every said amount of time (24 hours). Even if the user doesn't open/run the app.
Example: A mobile game named, "Cookie Clicker", kind of has this same functionality. It gives the user cookies depending on how much time was spent not playing the game/being offline.
My Question: How can I add +1 to a variable every 24 hours, regardless of if the user opens the app.
Code I Currently Have:
let daysOffDesfult = UserDefaults.standard
var daysOff = 0
//After 24 Hours: Call updateDaysOff() Function (This is the code I need.)
//Code Used to Save Variable Locally:
//Display the Updated Variable
if (daysOffDesfult.value(forKey: "daysOff") != nil){
daysOff = daysOffDesfult.value(forKey: "daysOff") as! NSInteger!
countLabel.text = "\(daysOff)"
}
//Update the Variable
func updateDaysOff() {
daysOff = daysOff + 1
countLabel.text = "\(daysOff)"
let daysOffDesfult = UserDefaults.standard
daysOffDesfult.setValue(daysOff, forKey: "daysOff")
daysOffDesfult.synchronize()
}
You can save the time of first open app in viewDidLoad or in your first UIViewController in UserDefaults like this:
if UserDefaults.standard.value(forKey: "firstDate") == nil {
UserDefaults.standard.set(Date(), forKey: "firstDate")
}
and every time app launches you compare the current date to date that you saved in UserDefaults to find out how many days passed
let savedDate = UserDefaults.standard.value(forKey: "firstDate")
let currentDate = Date()
let diffInDays = Calendar.current.dateComponents([.day], from: savedDate, to: currentDate).day
I want to retrieve steps from 1h ago. I don't need to do anything special, I just need to know how many steps a user has done since the last hour.
Even though my iPhone has some steps logged, the query to retrieve the number of steps returns "nil".
This is the code:
let calendar = Calendar.current //calendar now, to be used in calculating the h in the past
let beforeDate = calendar.date(byAdding: .hour, value: -1, to: Date())
let pedometer = CMPedometer() //define pedometer
if CMPedometer.isStepCountingAvailable() == true {print("steps available")}else{print("steps not available")}
pedometer.queryPedometerData(from: beforeDate!, to: Date(), withHandler: { (pedometerData, error) in
if let pedData = pedometerData{
self.dateLabel.text = "Steps:\(pedData.numberOfSteps)"
}else {
self.dateLabel.text = "error)"
print(beforeDate)
}
})
}
And this is the date format that I put in the query:
2018-03-16 12:59:17 +0000
What is wrong?
One possible problem is that your CMPedometer object is stored only in a local variable. pedometer.queryPedometerData runs asynchronously, so this object needs to persist long enough to fulfill the query. But it can't do that if it is a local variable; it vanishes before the data can even be fetched. Try making pedometer a persistent instance property instead.
Also be aware that you don't know what queue the data will be delivered on. You need to step out to the main queue in order to talk to the interface, and you are failing to do that.
ref.child(lastCallHistoryFetchPath).observeSingleEvent(of: .value, with: { (snapshot) in
if let lastFetch = snapshot.value as? [String : String]{
if let date = lastFetch["date"]{
self.mostRecentDateTime = DateStruct.callHistoryStrToDate(dateStr: date)
if let time = lastFetch["time"]{
self.mostRecentDateTime?.addTime(timeStr: time)
}
}
}
self.getCallHistory(number: myNumber, uuid: phoneNumberToUuidMappings[myNumber]!)
})
Even after deleting the value at lastCallHistoryPath in my backend (via firebase console), my ios application finds the old, deleted value here. Why is this happening and how can I prevent? I know there is a local DB stored but I thought the remotes entry would take precedence, especially since the deletion was the most recent operation.
I noticed that the downloaded timestamps of objects that are created by FIRServerValue.timestamp() are slightly different than the online version. My code:
let messagesRef = self.ref.child("messages").child(chatId)
messagesRef
.queryOrderedByChild("timestamp")
.queryLimitedToLast(500)
.observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
guard let object = snapshot.value as? [String: AnyObject] else { return }
let messageId = snapshot.key
if let
senderId = object["senderId"] as? String,
senderName = object["senderName"] as? String,
senderEmail = object["senderEmail"] as? String,
timestamp = object["timestamp"] as? NSTimeInterval {
let date = timestamp.toDate()
let text = object["text"] as? String
print("text: \(text) - timestamp: \(timestamp)")
}
})
Here is a sample output compared to the online value (marked by ->):
text: Optional("1") - timestamp: 1471596374007.0 -> 1471596374874
text: Optional("2") - timestamp: 1471596375044.0 -> 1471596375324
text: Optional("3") - timestamp: 1471596376157.0 -> 1471596376461
text: Optional("4") - timestamp: 1471596461213.0 -> 1471596463220
text: Optional("5") - timestamp: 1471596542659.0 -> 1471596543307
I sometimes encounter a bug where a messages comes before another message, even though it has been sent after that particular message. I assume it has something to do with this behaviour. When the retrieved timestamps are not exact, messages that are sent close to each other in time, can be ordered differently.
According to the documentation, when you specify the server timestamp the data sent to the server contains a:
Placeholder value for the number of milliseconds since the Unix epoch.
That placeholder will be replaced with the server's timestamp. However, if you have ChildAdded listeners on the devices that add data, those listeners are notified locally - and that's going to involve replacing the placeholder with a local timestamp. I suspect that's why you are seeing a difference between what's reported via the listener - when a message is written - and what shows up on the console. And that's also why the values that are read match those that are shown in the console.
Firebase likely takes into account the offset between the local and server clocks - as it accounts for this in the generation of keys for pushed data - but you are still going to get a small difference because the data is going to take some time to get to the server.
This local firing of events is also the reason you might sometimes see ChildAdded event firing in an unexpected order. I answered a question regarding unexpected snapshot ordering a few days ago.