Works fine when healthkit access was previously authorized on device but crashes otherwise - swift

override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
let healthKitTypes: Set = [
// access step count
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
]
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (_, _) in
print("authorized???")
}
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (bool, error) in
if let e = error {
print("oops something went wrong during authorization \(e.localizedDescription)")
} else {
print("User has completed the authorization flow")
}
}
getTodaysSteps { (result) in
print("\(result)")
self.steps = result
DispatchQueue.main.async {
if result == 0 {
self.StepDisplay.text = " You haven't walked"
} else {
self.StepDisplay.text = "\(result)"
}
}
}
getStepHistory()
}
func getStepHistory() {
let calendar = Calendar.current
var interval = DateComponents()
interval.day = 1
// Set the anchor date to Monday at 3:00 a.m.
var anchorComponents = calendar.dateComponents([.day, .month, .year, .weekday], from: Date())
let offset = (7 + (anchorComponents.weekday ?? 0) - 2) % 7
anchorComponents.day = (anchorComponents.day ?? 0) - offset
anchorComponents.hour = 0
anchorComponents.minute = 1
guard let anchorDate = calendar.date(from:anchorComponents) else {
fatalError("*** unable to create a valid date from the given components ***")
}
guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount) else {
fatalError("*** Unable to create a step count type ***")
}
This code works fine when the authorization is already given on the device. If however, it was not authorized earlier, it will not work unless getStepHistory() is commented out in viewDidLoad. I tried requesting additional authorization from within the getStepHistory() function but it doesn't solve the problem

You need to call getStepHistory inside the completion block to requestAuthorization if it has been authorized.
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (success, error) in
if let e = error {
print("oops something went wrong during authorization \(e.localizedDescription)")
} else if success {
print("User has granted access")
getStepHistory()
} else {
print("User has completed the authorization flow but there is no access")
}
}

Requesting the User's Permission
To request authorization, we invoke
requestAuthorization(toShare:,readTypes:,completion:) on the
HKHealthStore instance. This method accepts three parameters:
an optional set of HKSampleType objects
an optional set of HKObjectType objects
a completion handler with two parameters, a boolean indicating the result of the authorization request (successful or unsuccessful) and an optional error
It is important to understand that the boolean of the completion handler does not indicate whether the user granted or denied access to the requested health data types. It only informs the application whether the user responded to the application's authorization request. If the user dismissed the form by canceling the authorization request, the boolean of the completion handler is set to false.
In Your View Did Load :
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (success, error) in
if let err = error {
print("Error \(err.localizedDescription)")
} else if success {
// Get the Step Count....
getStepHistory()
} else {
print("No access to healthkit data")
}
}
Optionally You can try this function to get step count:
let healthStore = HKHealthStore()
func getTodaysSteps(completion: #escaping (Double) -> Void) {
let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: stepsQuantityType, quantitySamplePredicate: predicate, options: .cumulativeSum) { _, result, _ in
guard let result = result, let sum = result.sumQuantity() else {
completion(0.0)
return
}
completion(sum.doubleValue(for: HKUnit.count()))
}
healthStore.execute(query)
}

Related

Live heart rate record time interval - WatchOS

I have created a watch app with live heart rate record, for that i have used the workout session and HKAnchoredObjectQuery to fetch the results. In this apple has mentioned that, every 5 seconds will update the heart rate value, but in my case it will take upto 5-10 sec to update. I am using watch serious 3 and i need to collect the value for exactly every 5 seconds.
func startWorkout() {
if (session != nil) {
return
}
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .walking
workoutConfiguration.locationType = .outdoor
do {
session = try HKWorkoutSession(healthStore: healthStore, configuration: workoutConfiguration)
session?.delegate = self
} catch {
print("Unable to create the workout session!")
}
session?.startActivity(with: Date())
}
func startHeartRateStreamingQuery(_ workoutStartDate: Date){
guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else { return nil }
let datePredicate = HKQuery.predicateForSamples(withStart: workoutStartDate, end: nil, options: .strictEndDate )
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates:[datePredicate])
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
self.updateHeartRate(sampleObjects)
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.updateHeartRate(samples)
}
healthStore.execute(heartRateQuery)
}
func updateHeartRate(_ samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else {return}
for sample in heartRateSamples {
let timeStamp = sample.startDate
let value = sample.quantity
print("\(timeStamp)_\(value)")
}
}

Firebase getIDToken and how to use it in an API call

I have an API call that grabs json, but requires token authentication. Token auth works great, but when I try and pass the token along to the API function, it's coming back nil. I believe it's because Auth.auth().currentUser!.getIDToken(...) hasn't actually completed yet. Relevant code below... How do I modify this to
class SessionData : ObservableObject {
...
func token() -> String? {
var result: String? = nil
Auth.auth().currentUser!.getIDToken(completion: { (res, err) in
if err != nil {
print("*** TOKEN() ERROR: \(err!)")
} else {
print("*** TOKEN() SUCCESS: \(err!)")
result = res!
}
})
return result
}
...
}
class FetchPosts: ObservableObject {
#Published var posts = [Post]()
func load(api: Bool, session: SessionData) {
if api {
let url = URL(string: MyAPI.getAddress(token: session.token()!))!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let postsData = data {
// 3.
let decodedData = try JSONDecoder().decode(Response.self, from: postsData)
DispatchQueue.main.async {
self.posts = decodedData.result
if decodedData.error != nil {
print("ERROR: \(decodedData.error!)")
session.json_error(error: decodedData.error!)
}
}
} else {
print("No data. Connection error.")
DispatchQueue.main.async {
session.json_error(error: "Could not connect to server, please try again!")
}
}
} catch {
print("* Error: \(error)")
}
}.resume()
} else {
let url = Bundle.main.url(forResource: "test", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
if let products = try? decoder.decode([Post].self, from: data) {
self.posts = products
}
}
}
}
And this is how the .load function is called:
UserViewer(fetch: posts)
.transition(AnyTransition.slide)
.animation(.default)
.onAppear {
withAnimation{
posts.load(api: true, session: session)
}
}
.environmentObject(session)
Because getIDToken executes and returns asynchronously, you can't return directly from it. Instead, you'll need to use a callback function.
Here's a modification of your function:
func token(_ completion: #escaping (String?) -> ()) {
guard let user = Auth.auth().currentUser else {
//handle error
return
}
user.getIDToken(completion: { (res, err) in
if err != nil {
print("*** TOKEN() ERROR: \(err!)")
//handle error
} else {
print("*** TOKEN() SUCCESS: \(err!)")
completion(res)
}
})
}
Then, you can use it later on:
.onAppear {
session.token { token in
guard let token = token else {
//handle nil
return
}
withAnimation{
posts.load(api: true, session: session, token: token)
}
}
}
Modify your load to take a token parameter:
func load(api: Bool, session: SessionData, token: String) {
if api {
guard let url = URL(string: MyAPI.getAddress(token: token)) else {
//handle bad URL
return
}
Also, as you can see I'm doing in my code samples, I would try to get out of the habit of using ! to force unwrap optionals. If the optional is nil and you use !, your program will crash. Instead, familiarize yourself with guard let and if let and learn to handle optionals in a way that won't lead to a crash -- it's one of the great benefits of Swift.

Not getting Firebase token id when calling getIdToken in Swift 5 iOS

I have been trying to make this work for 3 days now, and I can't see where I am going. When trying to get firebase tokenId using the - (void)getIDTokenWithCompletion: (nullable void (^)(NSString *_Nullable __strong, NSError *_Nullable __strong))completion; function provided by firebase I am getting nothing in return.
I have created a separate class to get the Id using a completion handler. Below is the code I am using
import Foundation
import FirebaseAuth
class FirebaseToken {
static var shared = FirebaseToken.init()
func getIdToken(token completion: #escaping(String?,Error?) -> Void){
Auth.auth().currentUser?.getIDToken(completion:{ idToken, error in
guard let error = error else {return }
print(error)
completion(nil, error)
guard let token = idToken else {return}
completion(token, nil)
print(token)
})
}
}
This is the class I am using to call the func getIdToken function to get the Id, which is inside the func makeAPICall<T:Codable>(urlPath: String, apiMethod: HTTPMethod, expectedReturnType: T.Type,user completionHandler: #escaping ([T]?,Error?) function.
import Foundation
import Alamofire
import Firebase
class ApiService {
static var shared = ApiService.init()
let session: Session = {
let manager = ServerTrustManager(allHostsMustBeEvaluated: false,evaluators: ["localhost": DisabledTrustEvaluator()])
let configuration = URLSessionConfiguration.af.default
return Session(configuration: configuration, serverTrustManager: manager)
}()
//MARK:- GET
func makeAPICall<T:Codable>(urlPath: String, apiMethod: HTTPMethod, expectedReturnType: T.Type,user completionHandler: #escaping ([T]?,Error?) -> Void) {
var urlComponent = URLComponents()
urlComponent.scheme = "https"
urlComponent.host = "localhost"
urlComponent.port = 5001
urlComponent.path = "/api/" + urlPath
print(urlComponent.url!)
guard let url = urlComponent.url else {
return
}
var headers: HTTPHeaders?
FirebaseToken.shared.getIdToken(token: {tokenId, error in
guard let errors = error else {return}
print(errors)
guard let tokens = tokenId else {return}
headers = [
.authorization(bearerToken: tokens),
.accept("application/json")
]
})
guard let headerAuth = headers else {
print("not getting firebase token")
return
}
print(headerAuth)
session.request(url, method: apiMethod).validate().responseDecodable(of: [T].self) {(response) in
switch response.result{
case .success:
guard let users = response.value else {
return
}
//print(header)
completionHandler(users, nil)
case .failure(let error):
completionHandler(nil, error)
}
}
}
}
the variable var headers: HTTPHeaders?, which inside the function
guard let headerAuth = headers else {
print("not getting firebase token")
return
}
should be printing out the token yet, for some reason the token isn't being added. Can someone let me know where I am going wrong as I have been stuck for 3 days and I am still very new to firebase?
The printing result should be within print(headerAuth), however; I keep on getting the result within print("not getting firebase token")
The getIdToken method is an asynchronous call. Any code that needs the resulting token, needs to be inside the completion handler.
So something like:
FirebaseToken.shared.getIdToken(token: {tokenId, error in
guard let tokens = tokenId else {return}
headers = [
.authorization(bearerToken: tokens),
.accept("application/json")
]
guard let headerAuth = headers else {
print("not getting firebase token")
return
}
session.request(url, method: apiMethod).validate().responseDecodable(of: [T].self) {(response) in
switch response.result{
case .success:
guard let users = response.value else {
return
}
completionHandler(users, nil)
case .failure(let error):
completionHandler(nil, error)
}
}
})
I also removed this line:
guard let errors = error else {return}
As I'm pretty sure this returns out of the block when there is no error.

Update two fields at once with updateData

I am changing my online status with this code:
static func online(for uid: String, status: Bool, success: #escaping (Bool) -> Void) {
//True == Online, False == Offline
let db = Firestore.firestore()
let lastTime = Date().timeIntervalSince1970
let onlineStatus = ["onlineStatus" : status]
let lastTimeOnline = ["lastTimeOnline" : lastTime]
let ref = db.collection("users").document(uid)
ref.updateData(lastTimeOnline) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
ref.updateData(onlineStatus) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
}
I update the lastTimeOnline and the onlineStatus.
I listen to this updates via:
// Get the user online offline status
func getUserOnlineStatus(completion: #escaping (Dictionary<String, Any>) -> Void) {
let db = Firestore.firestore()
db.collection("users").addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .modified) {
//GETS CALLED TWICE BUT I ONLY WANT ONCE
print("modified called..")
guard let onlineStatus = diff.document.get("onlineStatus") as? Bool else {return}
guard let userId = diff.document.get("uid") as? String else {return}
var userIsOnline = Dictionary<String, Any>()
userIsOnline[userId] = [onlineStatus, "huhu"]
completion(userIsOnline)
}
}
}
}
The problem is now, since I use ref.updateData twice, my SnapshotListener .modified returns the desired data twice.
How can I update two fields in a single call, so my .modified just return one snapshot?
You can try to combine them
let all:[String:Any] = ["onlineStatus" : status ,"lastTimeOnline" : lastTime]
let ref = db.collection("users").document(uid)
ref.updateData(all) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}

flatMap Not returning onCompleted

I have created below function with chaining of multiple observables however whatever I do it does not seem to call completed ? it only return the following:
(facebookSignInAndFetchData()) -> subscribed
(facebookSignInAndFetchData()) -> Event next(())
even though when I debug the observables individually they all return completed
here is my chaining function
func facebookSignInAndFetchData() {
observerFacebook.flatMap { (provider: FacebookProvider) in
return provider.login()
}.flatMap { token in
return self.loginViewModel.rx_authenticate(token: token)
}.flatMap {
return self.loginViewModel.fetchProfileData()
}.debug().subscribe(onError: { error in
//Guard unknown ErrorType
guard let err = error as? AuthError else {
//Unknown error message
self.alertHelper.presentAlert(L10n.unknown)
return
}
//error message handling
switch err {
case .notLoggedIn:
print("not logged in")
break
default:
self.alertHelper.presentAlert(err.description)
}
}, onCompleted: {
self.goToInitialController()
}).addDisposableTo(self.disposeBag)
}
rx_authenticate
func rx_authenticate(token: String) -> Observable<Void> {
return Observable.create({ observer in
let credentials = SyncCredentials.facebook(token: token)
SyncUser.logIn(with: credentials, server: URL(string: Globals.serverURL)!, onCompletion: { user, error in
//Error while authenticating
guard error == nil else {
print("error while authenticating: \(error!)")
observer.onError(AuthError.unknown)
return
}
//Error while parsing user
guard let responseUser = user else {
print("error while authenticating: \(error!)")
observer.onError(AuthError.unknown)
return
}
//Authenticated
setDefaultRealmConfiguration(with: responseUser)
//next
observer.onNext()
//completed
observer.onCompleted()
})
return Disposables.create()
})
}
fetchProfileData
func fetchProfileData() -> Observable<Void> {
return Observable.create({ observer in
//Fetch facebookData
let params = ["fields" : "name, picture.width(480)"]
let graphRequest = GraphRequest(graphPath: "me", parameters: params)
graphRequest.start {
(urlResponse, requestResult) in
switch requestResult {
case .failed(_):
//Network error
observer.onError(AuthError.noConnection)
break
case .success(let graphResponse):
if let responseDictionary = graphResponse.dictionaryValue {
guard let identity = SyncUser.current?.identity else {
//User not logged in
observer.onError(AuthError.noUserIdentity)
return
}
//Name
let name = responseDictionary["name"] as! String
//Image dictionary
let pictureDic = responseDictionary["picture"] as! [String: Any]
let dataDic = pictureDic["data"] as! [String: Any]
let imageHeight = dataDic["height"] as! Int
let imageWidth = dataDic["width"] as! Int
let url = dataDic["url"] as! String
//Create Person object
let loggedUser = Person()
loggedUser.id = identity
loggedUser.name = name
//Create photo object
let photo = Photo()
photo.height = imageHeight
photo.width = imageWidth
photo.url = url
//Append photo object to person object
loggedUser.profileImage = photo
//Save userData
let realm = try! Realm()
try! realm.write {
realm.add(loggedUser, update: true)
}
//next
observer.onNext()
//completed
observer.onCompleted()
} else {
//Could not retrieve responseData
observer.onError(AuthError.noResponse)
}
}
}
return Disposables.create()
})
}
observerFacebook
//FacebookProvider
private lazy var observerFacebook: Observable<FacebookProvider>! = {
self.facebookButton.rx.tap.map {
return FacebookProvider(parentController: self)
}
}()
The chain starts with calling observerFacebook, which returns an observable that will emit values everytime facebookButton is tapped.
This observable will only complete when facebookButton gets released, most probably when the view controller holding it is removed from screen.
The rest of the chain will map or flatMap, but never force completion as another tap will trigger the whole chain again.
The easy way to solve this would be to add a call to take(1) on facebookButton.rx.tap, so that the function would be defined like so:
private lazy var observerFacebook: Observable<FacebookProvider>! = {
self.facebookButton.rx.tap
.take(1)
.map {
return FacebookProvider(parentController: self)
}
}()
Now, observerFacebook will complete after the first tap and you should see a call to onCompleted.
Note that you'll need to resubscribe to the chain on errors if you want to perform it again when another tap comes in.