Search code examples
swiftdatabasefirebasecore-motion

Swift - Store Dates of Step Counter in Firebase Database


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)
        }
    }
}

Solution

  • 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.