I've created a pedometer that stores in a firebase database. However it only stores step data for that one day and when I start another day the steps disappear. I'm guessing I would need to store this data under separate dates to see how many steps were taken each day. I cannot work out how to implement this into my own code. Any help in how to add the date to separate child nodes would be appreciated.
import UIKit
import CoreMotion
import Dispatch
import Firebase
class PedometerViewController: UIViewController {
// Declarations of Pedometer
private let activityManager = CMMotionActivityManager()
private let pedometer = CMPedometer()
private var shouldStartUpdating: Bool = false
private var startDate: Date? = nil
var stepNumberVule: Int = 0
// Storyboard connections
@IBOutlet weak var startButton: UIButton!
@IBOutlet weak var stepsCountLabel: UILabel!
@IBOutlet weak var activityTypeLabel: UILabel!
private func updateStepCounterValue(_ numberOfSteps: NSNumber) {
stepNumberVule = numberOfSteps.intValue
stepsCountLabel.text = numberOfSteps.stringValue
}
func getPedValue() {
var ref: DatabaseReference!
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
ref.child("user").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let ped:Int = value?["pedometer"] as? Int ?? 0
self.stepNumberVule = ped
self.stepsCountLabel.text = ("\(self.stepNumberVule)")
}) { (error) in
print(error.localizedDescription)
}
}
@IBAction func Save(_ sender: Any) {
var ref: DatabaseReference!
ref = Database.database().reference()
let user = Auth.auth().currentUser!.uid
let key = ref.child("user").child(user)
ref.child("user/\(user)/pedometer").setValue(self.stepNumberVule)
}
// Do any additional setup after loading the view.
override func viewDidLoad() {
super.viewDidLoad()
startButton.addTarget(self, action: #selector(didTapStartButton), for: .touchUpInside)
}
// Do additional tasks associated with presenting the view
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let startDate = startDate else { return }
updateStepsCountLabelUsing(startDate: startDate)
}
@IBAction func Ready(_ sender: AnyObject) {
self.startButton.isHidden = true
}
// Ready? button tapped
@objc private func didTapStartButton() {
let popOverVC = UIStoryboard(name: "Tab Bar", bundle: nil).instantiateViewController(withIdentifier: "PopUp") as! PopUpViewController
self.addChildViewController(popOverVC)
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParentViewController: self)
shouldStartUpdating = !shouldStartUpdating
shouldStartUpdating ? (onStart()) : (onStop())
}
}
// Pedometer Extension and its private fuctions (functions are explained explicitly so no need for comments)
extension PedometerViewController {
private func onStart() {
startButton.setTitle("Stop", for: .normal)
startDate = Date()
checkAuthorizationStatus()
startUpdating()
}
private func onStop() {
startButton.setTitle("Start", for: .normal)
startDate = nil
stopUpdating()
}
private func startUpdating() {
if CMMotionActivityManager.isActivityAvailable() {
startTrackingActivityType()
} else {
activityTypeLabel.text = "Not available"
}
if CMPedometer.isStepCountingAvailable() {
startCountingSteps()
} else {
stepsCountLabel.text = "Not available"
}
}
private func checkAuthorizationStatus() {
switch CMMotionActivityManager.authorizationStatus() {
case CMAuthorizationStatus.denied:
onStop()
activityTypeLabel.text = "Not available"
stepsCountLabel.text = "Not available"
default:break
}
}
private func stopUpdating() {
activityManager.stopActivityUpdates()
pedometer.stopUpdates()
pedometer.stopEventUpdates()
}
private func on(error: Error) {
//handle error
}
// Update step count
private func updateStepsCountLabelUsing(startDate: Date) {
pedometer.queryPedometerData(from: startDate, to: Date()) {
[weak self] pedometerData, error in
if let error = error {
self?.on(error: error)
} else if let pedometerData = pedometerData {
DispatchQueue.main.async {
self?.updateStepCounterValue(pedometerData.numberOfSteps)
}
}
}
}
// Activity type
private func startTrackingActivityType() {
activityManager.startActivityUpdates(to: OperationQueue.main) {
[weak self] (activity: CMMotionActivity?) in
guard let activity = activity else { return }
DispatchQueue.main.async {
if activity.walking {
self?.activityTypeLabel.text = "You're walking"
} else if activity.stationary {
self?.activityTypeLabel.text = "You're still"
} else if activity.running {
self?.activityTypeLabel.text = "You're running"
} else if activity.automotive {
self?.activityTypeLabel.text = "Driving"
}
}
}
}
// Start counting steps
private func startCountingSteps() {
pedometer.startUpdates(from: Date()) {
[weak self] pedometerData, error in
guard let pedometerData = pedometerData, error == nil else { return }
DispatchQueue.main.async {
self?.updateStepCounterValue(pedometerData.numberOfSteps)
}
}
}
I think this may help get you going the right direction, if not, let me know and I will edit.
Here's the 10k foot level concept.
Your step data needs to be stored in separate nodes so a sample Firebase structure would look like this
users
uid_0
name: "Connor"
steps:
-asjs89u8d094f
date: 20180320
pedometer: 74
-u999d9ks90dfk
date: 20180321
pedometer: 82
an instead of this
ref.child("user/(user)/pedometer").setValue(self.stepNumberVule)
it would be something like this
let thisUsersStepsRef = your_firebase.child("users").child(uid).child("steps")
let refToAdd = thisUsersStepsRef.childByAutoId()
refToAdd.setValue(["date": "20180320", "pedometer", "74")
which will write a structure like this within the uid_0 node.
steps:
-asjs89u8d094f
date: 20180320
pedometer: 74
the -asjd...and -u999... keys are created with childByAutoId and I just used those strings as placeholders.
Storing it in this fashion allows each days steps to recorded separately. It's generally often a good idea to disassociate node names (keys) from the child data it contains; it's make changing things a lot easier long term and make it much more manageable and expandable.